Add support for extended attributes: draft-dekok-radext-radius-extensions
authorAlan T. DeKok <aland@freeradius.org>
Thu, 9 Sep 2010 13:29:29 +0000 (15:29 +0200)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 9 Sep 2010 13:29:29 +0000 (15:29 +0200)
We can encode / decode all non-TLV types without a problem.
TLVs are currently limited to one level (241.1.2), and to the
length of the encapsulating RADIUS attribute.

The "M" flag for extended attributes with flags is not supported.

share/dictionary
share/dictionary.extended [new file with mode: 0644]
src/include/libradius.h
src/include/radius.h
src/lib/dict.c
src/lib/radius.c

index dad1f42..5cb0e2f 100644 (file)
@@ -82,6 +82,7 @@ $INCLUDE dictionary.rfc5176
 $INCLUDE dictionary.rfc5580
 $INCLUDE dictionary.rfc5607
 $INCLUDE dictionary.rfc5904
+$INCLUDE dictionary.extended
 
 #
 #      Include vendor dictionaries after the standard ones.
diff --git a/share/dictionary.extended b/share/dictionary.extended
new file mode 100644 (file)
index 0000000..dee13a3
--- /dev/null
@@ -0,0 +1,23 @@
+ATTRIBUTE      Extended-Attribute-1    241     octets extended
+ATTRIBUTE      Extended-Attribute-2    242     octets extended
+ATTRIBUTE      Extended-Attribute-3    243     octets extended
+ATTRIBUTE      Extended-Attribute-4    244     octets extended
+ATTRIBUTE      Extended-Attribute-5    245     octets extended-flags
+ATTRIBUTE      Extended-Attribute-6    246     octets extended-flags
+
+ATTRIBUTE      Extended-Vendor-Specific-1      241.26  octets
+ATTRIBUTE      Extended-Vendor-Specific-2      242.26  octets
+ATTRIBUTE      Extended-Vendor-Specific-3      243.26  octets
+ATTRIBUTE      Extended-Vendor-Specific-4      244.26  octets
+ATTRIBUTE      Extended-Vendor-Specific-5      245.26  octets
+ATTRIBUTE      Extended-Vendor-Specific-6      246.26  octets
+
+ATTRIBUTE      Extended-Example-1              241.1   integer
+ATTRIBUTE      Extended-Example-2              241.2   ipaddr
+ATTRIBUTE      Extended-Example-3              241.3   tlv
+ATTRIBUTE      Extended-Example-TLV-3-1        241.3.1 integer
+ATTRIBUTE      Extended-Example-TLV-3-2        241.3.2 string
+ATTRIBUTE      Extended-Example-4              241.4   string
+
+ATTRIBUTE      Extended-Example-Flags-1        245.1 integer
+ATTRIBUTE      Extended-Example-Flags-2        245.2 ipaddr
index 1d12fd7..e7cdbc6 100644 (file)
@@ -104,6 +104,8 @@ typedef struct attr_flags {
        unsigned int            has_tlv : 1; /* has sub attributes */
        unsigned int            is_tlv : 1; /* is a sub attribute */
        unsigned int            encoded : 1; /* has been put into packet */
+       unsigned int            extended : 1; /* extended attribute */
+       unsigned int            extended_flags : 1; /* with flag */
 
        int8_t                  tag;          /* tag for tunneled attributes */
        uint8_t                 encrypt;      /* encryption method */
index 7d784c3..c6eaad2 100644 (file)
 #define VENDORPEC_MICROSOFT    311
 #define VENDORPEC_FREERADIUS   11344
 #define VENDORPEC_WIMAX                24757
+#define VENDORPEC_EXTENDED     (1 << 25)
 
 /*
  * Vendor specific attributes
index 6c14414..ff77e21 100644 (file)
@@ -546,12 +546,33 @@ int dict_addattr(const char *name, int attr, int vendor, int type,
                }
        }
 
+       /*
+        *      Additional checks for extended attributes.
+        */
+       if (flags.extended || flags.extended_flags) {
+               if (vendor != 0) {
+                       fr_strerror_printf("dict_addattr: VSAs cannot use the \"extended\" attribute format.");
+                       return -1;
+               }
+               vendor = VENDORPEC_EXTENDED;
+
+               if ((attr < 256) && (type != PW_TYPE_OCTETS)) {
+                       fr_strerror_printf("dict_addattr: The base \"extended\" attribute definition MUST be of type \"octets\".");
+                       return -1;
+               }
+
+               if (flags.has_tag || flags.array || (flags.encrypt != FLAG_ENCRYPT_NONE)) {
+                       fr_strerror_printf("dict_addattr: The \"extended\" attributes MUST NOT have any flags set.");
+                       return -1;
+               }
+       }
+
        if (attr < 0) {
                fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (less than zero)");
                return -1;
        }
 
-       if (vendor) {
+       if (vendor && (vendor != VENDORPEC_EXTENDED)) {
                DICT_VENDOR *dv;
                static DICT_VENDOR *last_vendor = NULL;
 
@@ -616,7 +637,6 @@ int dict_addattr(const char *name, int attr, int vendor, int type,
        da->vendor = vendor;
        da->type = type;
        da->flags = flags;
-       da->vendor = vendor;
 
        /*
         *      Insert the attribute, only if it's not a duplicate.
@@ -833,7 +853,7 @@ static int sscanf_i(const char *str, int *pvalue)
 {
        int rcode = 0;
        int base = 10;
-       const char *tab = "0123456789";
+       static const char *tab = "0123456789";
 
        if ((str[0] == '0') &&
            ((str[1] == 'x') || (str[1] == 'X'))) {
@@ -871,6 +891,7 @@ static int process_attribute(const char* fn, const int line,
        int             type;
        int             length = 0;
        ATTR_FLAGS      flags;
+       char            *p;
 
        if ((argc < 3) || (argc > 4)) {
                fr_strerror_printf("dict_init: %s[%d]: invalid ATTRIBUTE line",
@@ -878,6 +899,14 @@ static int process_attribute(const char* fn, const int line,
                return -1;
        }
 
+       memset(&flags, 0, sizeof(flags));
+
+       /*
+        *      Look for extended attributes before doing anything else.
+        */
+       p = strchr(argv[1], '.');
+       if (p) *p = '\0';
+
        /*
         *      Validate all entries
         */
@@ -886,6 +915,101 @@ static int process_attribute(const char* fn, const int line,
                return -1;
        }
 
+       /*
+        *      Parse extended attributes.
+        */
+       if (p) {
+               int sub;
+               char *q;
+               DICT_ATTR *da;
+
+               *p = '.';       /* reset forlater printing */
+
+               /*
+                *      Does the parent attribute exist?
+                */
+               da = dict_attrbyvalue(value, VENDORPEC_EXTENDED);
+               if (!da) {
+                       fr_strerror_printf("dict_init: %s[%d]: Entry refers to unknown attribute %d", fn, line, value);
+                       return -1;
+               }
+
+               /*
+                *      241.1 means 241 is of type "extended".
+                *      Otherwise, die.
+                */
+               if (!da->flags.extended && !da->flags.extended_flags) {
+                       fr_strerror_printf("dict_init: %s[%d]: Entry refers to a non-extended attribute %d", fn, line, value);
+                       return -1;
+               }
+
+               /*
+                *      Look for sub-TLVs
+                */
+               q = strchr(p + 1, '.');
+               if (q) *q = '\0';
+
+               /*
+                *      Parse error.
+                */
+               if (!sscanf_i(p + 1, &sub)) {
+                       fr_strerror_printf("dict_init: %s[%d]: Parse error in value \"%s\"", fn, line, argv[1]);
+                       return -1;
+               }
+
+               /*
+                *      Value is out of bounds.
+                */
+               if ((sub == 0) || (sub > 255)) {
+                       fr_strerror_printf("dict_init: %s[%d]: Entry has value out of range 0..255: %d", fn, line, sub);
+                       return -1;
+               }
+
+               value |= (sub << fr_wimax_shift[1]);
+
+               /*
+                *      If this is defining the contents of a TLV,
+                *      look for the parent, and check it.
+                */
+               if (q) {
+                       DICT_ATTR *tlv;
+
+                       tlv = dict_attrbyvalue(value, VENDORPEC_EXTENDED);
+                       if (!tlv || !tlv->flags.has_tlv ||
+                           (!tlv->flags.extended && !tlv->flags.extended_flags)) {
+                               fr_strerror_printf("dict_init: %s[%d]: Entry refers to Attribute \"%s\", which is not an extended attribute TLV", fn, line, argv[1]);
+                               return -1;
+
+                       }
+
+                       flags.is_tlv = 1;
+                       
+                       /*
+                        *      Parse error.
+                        */
+                       if (!sscanf_i(q + 1, &sub)) {
+                               fr_strerror_printf("dict_init: %s[%d]: Parse error in value \"%s\"", fn, line, argv[1]);
+                               return -1;
+                       }
+
+                       /*
+                        *      Value is out of bounds.
+                        */
+                       if ((sub == 0) || (sub > 255)) {
+                               fr_strerror_printf("dict_init: %s[%d]: Entry has value out of range 0..255: %d", fn, line, sub);
+                               return -1;
+                       }
+
+                       value |= (sub << fr_wimax_shift[2]);
+               }
+
+               /*
+                *      Set which type of attribute this is.
+                */
+               flags.extended = da->flags.extended;
+               flags.extended_flags = da->flags.extended_flags;
+       }
+
        if (strncmp(argv[2], "octets[", 7) != 0) {
                /*
                 *      find the type of the attribute.
@@ -897,7 +1021,6 @@ static int process_attribute(const char* fn, const int line,
                        return -1;
                }
        } else {
-               char *p;
                type = PW_TYPE_OCTETS;
                
                p = strchr(argv[2] + 7, ']');
@@ -923,7 +1046,6 @@ static int process_attribute(const char* fn, const int line,
         *      Only look up the vendor if the string
         *      is non-empty.
         */
-       memset(&flags, 0, sizeof(flags));
        if (argc < 4) {
                /*
                 *      Force "length" for data types of fixed length;
@@ -993,7 +1115,7 @@ static int process_attribute(const char* fn, const int line,
                                        return -1;
                                }
                                
-                       } else if (strncmp(key, "array", 8) == 0) {
+                       } else if (strncmp(key, "array", 6) == 0) {
                                flags.array = 1;
                                
                                switch (type) {
@@ -1018,6 +1140,23 @@ static int process_attribute(const char* fn, const int line,
                                   ((vendor = dict_vendorbyname(key)) !=0)) {
                                break;
 
+                       } else if (strncmp(key, "extended-flags", 15) == 0) {
+                               if (flags.extended) {
+                                       fr_strerror_printf( "dict_init: %s[%d] You cannot set two  \"extended\" flags.",
+                                                           fn, line);
+                                       return -1;
+                               }
+
+                               flags.extended_flags = 1;
+
+                       } else if (strncmp(key, "extended", 9) == 0) {
+                               if (flags.extended_flags) {
+                                       fr_strerror_printf( "dict_init: %s[%d] You cannot set two  \"extended\" flags.",
+                                                           fn, line);
+                                       return -1;
+                               }
+                               flags.extended = 1;
+
                        } else {
                                fr_strerror_printf( "dict_init: %s[%d]: unknown option \"%s\"",
                                            fn, line, key);
@@ -1074,7 +1213,6 @@ static int process_attribute(const char* fn, const int line,
                flags.is_tlv = 1;
        }
 
-
        /*
         *      Add it in.
         */
index be4124f..62751df 100644 (file)
@@ -816,7 +816,8 @@ static int rad_vp2rfc(const RADIUS_PACKET *packet,
        ptr[0] = attribute & 0xff; /* NOT vp->attribute */
        ptr[1] = 2;
 
-       len = vp2data(packet, original, secret, vp, ptr + 2, room - 2);
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+       len = vp2data(packet, original, secret, vp, ptr + 2, room);
        if (len < 0) return len;
 
        ptr[1] += len;
@@ -921,6 +922,52 @@ static int wimax2data(const RADIUS_PACKET *packet,
 }
 
 
+static int rad_vp2extended(const RADIUS_PACKET *packet,
+                     const RADIUS_PACKET *original,
+                     const char *secret, const VALUE_PAIR *vp,
+                     unsigned int attribute, uint8_t *ptr, size_t room)
+{
+       int len = 2;
+
+       if (room < 3) return 0;
+
+       ptr[0] = attribute & 0xff; /* NOT vp->attribute */
+       ptr[1] = 3;
+
+       if (vp->flags.extended) {
+               ptr[2] = (attribute & 0xff00) >> 8;
+               len++;
+
+       } else if (vp->flags.extended_flags) {
+               if (room < 4) return 0;
+
+               ptr[1] = 4;
+               ptr[2] = (attribute & 0xff00) >> 8;
+               ptr[3] = 0;
+
+               len += 2;
+       }
+
+       /*
+        *      For now, no extended attribute can be longer than the
+        *      encapsulating attribute.  Once we add support for the
+        *      "M" bit, this restriction will be relaxed.
+        */
+       if (room > (255 - ptr[1])) room = 255 - ptr[1];
+
+       if (!vp->flags.is_tlv) {
+               len = vp2data(packet, original, secret, vp, ptr + ptr[1], room);
+       } else {
+               len = tlv2data(packet, original, secret, vp, ptr + ptr[1], room, 2);
+       }
+
+       if (len < 0) return len;
+
+       ptr[1] += len;
+
+       return ptr[1];
+}
+
 /*
  *     Parse a data structure into a RADIUS attribute.
  */
@@ -941,6 +988,11 @@ int rad_vp2attr(const RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                                  vp->attribute, start, room);
        }
 
+       if (vp->vendor == VENDORPEC_EXTENDED) {
+               return rad_vp2extended(packet, original, secret, vp,
+                                      vp->attribute, start, room);
+       }
+
        /*
         *      Not enough room for:
         *              attr, len, vendor-id, vsa, vsalen
@@ -1218,10 +1270,12 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
         */
        for (reply = packet->vps; reply; reply = reply->next) {
                /*
-                *      Ignore non-wire attributes
+                *      Ignore non-wire attributes, but allow extended
+                *      attributes.
                 */
                if ((reply->vendor == 0) &&
-                   ((reply->attribute & 0xFFFF) > 0xff)) {
+                   ((reply->attribute & 0xFFFF) >= 256) &&
+                   !reply->flags.extended && !reply->flags.extended_flags) {
 #ifndef NDEBUG
                        /*
                         *      Permit the admin to send BADLY formatted
@@ -1263,7 +1317,7 @@ int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original,
                 */
                if (reply->flags.encoded) continue;
 
-               if (reply->flags.is_tlv) {
+               if ((reply->vendor == VENDORPEC_WIMAX) && reply->flags.is_tlv) {
                        len = rad_encode_wimax(packet, original, secret,
                                               reply, ptr,
                                               ((uint8_t *) data) + sizeof(data) - ptr);
@@ -2786,6 +2840,31 @@ static VALUE_PAIR *rad_continuation2vp(const RADIUS_PACKET *packet,
 
 
 /*
+ *     Extended attribute TLV to VP.
+ */
+static VALUE_PAIR *tlv2vp(const RADIUS_PACKET *packet,
+                           const RADIUS_PACKET *original,
+                           const char *secret, int attribute,
+                           int length, const uint8_t *data)
+{
+       VALUE_PAIR *vp;
+
+       if ((length < 2) || (data[1] < 2)) return NULL;
+
+       /*
+        *      For now, only one TLV is allowed.
+        */
+       if (data[1] != length) return NULL;
+
+       attribute |= (data[0] << fr_wimax_shift[2]);
+
+       vp = paircreate(attribute, VENDORPEC_EXTENDED, PW_TYPE_OCTETS);
+       if (!vp) return NULL;
+
+       return data2vp(packet, original, secret, length - 2, data + 2, vp);
+}
+
+/*
  *     Parse a RADIUS attribute into a data structure.
  */
 VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet,
@@ -2795,6 +2874,50 @@ VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet,
 {
        VALUE_PAIR *vp;
 
+       /*
+        *      Hard-coded values are bad...
+        */
+       if ((vendor == 0) && (attribute >= 241) && (attribute <= 246)) {
+               DICT_ATTR *da;
+
+               da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+               if (da && (da->flags.extended || da->flags.extended_flags)) {
+
+                       if (length == 0) return NULL;
+
+                       attribute |= (data[0] << fr_wimax_shift[1]);
+                       vendor = VENDORPEC_EXTENDED;
+
+                       data++;
+                       length--;
+
+                       /*
+                        *      There may be a flag octet.
+                        */
+                       if (da->flags.extended_flags) {
+                               if (length == 0) return NULL;
+
+                               /*
+                                *      If there's a flag, we can't
+                                *      handle it.
+                                */
+                               if (data[0] != 0) return NULL;
+                               data++;
+                               length--;
+                       }
+
+                       /*
+                        *      Now look up the extended attribute, to
+                        *      see if it's a TLV carrying more data.
+                        *      
+                        */
+                       da = dict_attrbyvalue(attribute, VENDORPEC_EXTENDED);
+                       if (da && da->flags.has_tlv) {
+                               return tlv2vp(packet, original, secret,
+                                             attribute, length, data);
+                       }
+               }
+       }
        vp = paircreate(attribute, vendor, PW_TYPE_OCTETS);
        if (!vp) return NULL;