diff --git a/qse/include/qse/cmn/StrBase.hpp b/qse/include/qse/cmn/StrBase.hpp index cff5ae05..07794e24 100644 --- a/qse/include/qse/cmn/StrBase.hpp +++ b/qse/include/qse/cmn/StrBase.hpp @@ -423,6 +423,51 @@ public: return SelfType (this->_item->buffer + index, size); } + void truncate (qse_size_t new_size) + { + // use this function with care. + // if the new size is greater than the current capacity, + // it grows the capacity first according to the policy. + // in such a case, it still changes the length. + // if the new size is equal to or less than the current capaciy, + // it only changes the length. + // when chaning the length, it puts a null character at + // the length position. it leaves the contents of the growing + // part or the shrinking part untouched. + // + // if you want to shorten the length while growing the capacity, + // you can call truncate twice. for instance, + // str.truncate (1000). + // str.truncate (100). + + StringItem* old_item = QSE_NULL; + + if (this->_item->isShared()) + { + StringItem* t; + + if (new_size > this->_item->capacity) + t = this->_item->copy (this->getMmgr(), this->adjust_desired_capacity(new_size)); + else + t = this->_item->copy (this->getMmgr()); + + old_item = this->_item; + this->_item = t; + this->ref_item (); + } + else if (new_size > this->_item->capacity) + { + StringItem* t = this->_item->copy (this->getMmgr(), this->adjust_desired_capacity(new_size)); + old_item = this->_item; + this->_item = t; + this->ref_item ();; + } + + this->_item->buffer[new_size] = NULL_CHAR; + this->_item->size = new_size; + if (old_item) this->deref_item (old_item); + } + // // TODO: comparison, hash, trim, case-converting, etc // utf-8 encoding/decoding diff --git a/qse/include/qse/rad/Makefile.am b/qse/include/qse/rad/Makefile.am index b2f37fdf..97178568 100644 --- a/qse/include/qse/rad/Makefile.am +++ b/qse/include/qse/rad/Makefile.am @@ -1,5 +1,6 @@ pkgincludedir = $(includedir)/qse/rad pkginclude_HEADERS = \ + raddic.h \ radmsg.h diff --git a/qse/include/qse/rad/Makefile.in b/qse/include/qse/rad/Makefile.in index 91a424d4..d85ee794 100644 --- a/qse/include/qse/rad/Makefile.in +++ b/qse/include/qse/rad/Makefile.in @@ -357,6 +357,7 @@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ pkginclude_HEADERS = \ + raddic.h \ radmsg.h all: all-am diff --git a/qse/include/qse/rad/raddic.h b/qse/include/qse/rad/raddic.h new file mode 100644 index 00000000..e491614f --- /dev/null +++ b/qse/include/qse/rad/raddic.h @@ -0,0 +1,79 @@ +/* + * $Id$ + * + Copyright (c) 2006-2014 Chung, Hyung-Hwan. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QSE_RAD_RADDIC_H_ +#define _QSE_RAD_RADDIC_H_ + +#include +#include + +#define QSE_RAD_ATTR_TYPE_STRING 0 +#define QSE_RAD_ATTR_TYPE_INTEGER 1 +#define QSE_RAD_ATTR_TYPE_IPADDR 2 +#define QSE_RAD_ATTR_TYPE_DATE 3 +#define QSE_RAD_ATTR_TYPE_ABINARY 4 +#define QSE_RAD_ATTR_TYPE_OCTETS 5 +#define QSE_RAD_ATTR_TYPE_IFID 6 +#define QSE_RAD_ATTR_TYPE_IPV6ADDR 7 +#define QSE_RAD_ATTR_TYPE_IPV6PREFIX 8 +#define QSE_RAD_ATTR_TYPE_BYTE 9 +#define QSE_RAD_ATTR_TYPE_SHORT 10 +#define QSE_RAD_ATTR_TYPE_ETHERNET 11 +#define QSE_RAD_ATTR_TYPE_SIGNED 12 +#define QSE_RAD_ATTR_TYPE_COMBO_IP 13 +#define QSE_RAD_ATTR_TYPE_TLV 14 + +struct qse_raddic_attr_t +{ + int attr; + int type; + int vendor; +/* ATTR_FLAGS flags;*/ + char name[1]; +}; +typedef struct qse_raddic_attr_t qse_raddic_attr_t; + +typedef struct qse_raddic_t qse_raddic_t; + +#if defined(__cplusplus) +extern "C" { +#endif + +QSE_EXPORT qse_raddic_t* qse_raddic_open ( + qse_mmgr_t* mmgr, + qse_size_t xtnsize +); + +QSE_EXPORT void qse_raddic_close ( + qse_raddic_t* dic +); + +#if defined(__cplusplus) +} +#endif + + +#endif diff --git a/qse/include/qse/rad/radmsg.h b/qse/include/qse/rad/radmsg.h index cfd632ab..418f62f9 100644 --- a/qse/include/qse/rad/radmsg.h +++ b/qse/include/qse/rad/radmsg.h @@ -229,7 +229,7 @@ QSE_EXPORT int qse_rad_delete_attribute ( QSE_EXPORT int qse_rad_delete_vendor_specific_attribute ( qse_rad_hdr_t* hdr, qse_uint32_t vendor, - qse_uint8_t id + qse_uint8_t attrid ); QSE_EXPORT int qse_rad_insert_string_attribute ( @@ -284,7 +284,7 @@ QSE_EXPORT int qse_rad_insert_ipv6prefix_attribute ( ); QSE_EXPORT int qse_rad_insert_giga_attribute ( - qse_rad_hdr_t* auth, + qse_rad_hdr_t* auth, int max, qse_uint32_t vendor, int low_id, @@ -293,7 +293,7 @@ QSE_EXPORT int qse_rad_insert_giga_attribute ( ); QSE_EXPORT int qse_rad_set_user_password ( - qse_rad_hdr_t* auth, + qse_rad_hdr_t* auth, int max, const qse_mchar_t* password, const qse_mchar_t* secret @@ -309,7 +309,7 @@ QSE_EXPORT void qse_rad_copy_authenticator ( ); QSE_EXPORT int qse_rad_set_authenticator ( - qse_rad_hdr_t* req, + qse_rad_hdr_t* req, const qse_mchar_t* secret ); @@ -319,14 +319,14 @@ QSE_EXPORT int qse_rad_set_authenticator ( * so this function doesn't apply */ QSE_EXPORT int qse_rad_verify_request ( - qse_rad_hdr_t* req, + qse_rad_hdr_t* req, const qse_mchar_t* secret ); QSE_EXPORT int qse_rad_verify_response ( qse_rad_hdr_t* res, const qse_rad_hdr_t* req, - const qse_mchar_t* secret + const qse_mchar_t* secret ); diff --git a/qse/lib/rad/Makefile.am b/qse/lib/rad/Makefile.am index e20b1e19..033d1836 100644 --- a/qse/lib/rad/Makefile.am +++ b/qse/lib/rad/Makefile.am @@ -8,6 +8,7 @@ AM_CPPFLAGS = \ lib_LTLIBRARIES = libqserad.la libqserad_la_SOURCES = \ + raddic.c \ radmsg.c libqserad_la_CFLAGS = diff --git a/qse/lib/rad/Makefile.in b/qse/lib/rad/Makefile.in index bf0fda51..e5391f81 100644 --- a/qse/lib/rad/Makefile.in +++ b/qse/lib/rad/Makefile.in @@ -135,7 +135,8 @@ am__uninstall_files_from_dir = { \ am__installdirs = "$(DESTDIR)$(libdir)" LTLIBRARIES = $(lib_LTLIBRARIES) libqserad_la_DEPENDENCIES = -am_libqserad_la_OBJECTS = libqserad_la-radmsg.lo +am_libqserad_la_OBJECTS = libqserad_la-raddic.lo \ + libqserad_la-radmsg.lo libqserad_la_OBJECTS = $(am_libqserad_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) @@ -396,6 +397,7 @@ AM_CPPFLAGS = \ #noinst_HEADERS = lib_LTLIBRARIES = libqserad.la libqserad_la_SOURCES = \ + raddic.c \ radmsg.c libqserad_la_CFLAGS = @@ -479,6 +481,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libqserad_la-raddic.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libqserad_la-radmsg.Plo@am__quote@ .c.o: @@ -502,6 +505,13 @@ distclean-compile: @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< +libqserad_la-raddic.lo: raddic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libqserad_la_CFLAGS) $(CFLAGS) -MT libqserad_la-raddic.lo -MD -MP -MF $(DEPDIR)/libqserad_la-raddic.Tpo -c -o libqserad_la-raddic.lo `test -f 'raddic.c' || echo '$(srcdir)/'`raddic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libqserad_la-raddic.Tpo $(DEPDIR)/libqserad_la-raddic.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='raddic.c' object='libqserad_la-raddic.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libqserad_la_CFLAGS) $(CFLAGS) -c -o libqserad_la-raddic.lo `test -f 'raddic.c' || echo '$(srcdir)/'`raddic.c + libqserad_la-radmsg.lo: radmsg.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libqserad_la_CFLAGS) $(CFLAGS) -MT libqserad_la-radmsg.lo -MD -MP -MF $(DEPDIR)/libqserad_la-radmsg.Tpo -c -o libqserad_la-radmsg.lo `test -f 'radmsg.c' || echo '$(srcdir)/'`radmsg.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libqserad_la-radmsg.Tpo $(DEPDIR)/libqserad_la-radmsg.Plo diff --git a/qse/lib/rad/raddic.c b/qse/lib/rad/raddic.c new file mode 100644 index 00000000..4761b0f6 --- /dev/null +++ b/qse/lib/rad/raddic.c @@ -0,0 +1,2285 @@ +/* + * $Id$ + * + Copyright (c) 2006-2014 Chung, Hyung-Hwan. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * dict.c Routines to read the dictionary file. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +#include +#include +#include +#include "../cmn/mem-prv.h" +#include + +struct qse_raddic_t +{ + qse_mmgr_t* mmgr; + + qse_rbt_t vendors_byname; + qse_rbt_t vendors_byvalue; + qse_rbt_t attributes_byname; + qse_rbt_t attributes_byvalue; + qse_rbt_t values_byvalue; + qse_rbt_t values_byname; +}; + + +typedef struct name_id_t name_id_t; +struct name_id_t +{ + const char *name; + int id; +}; + +static const name_id_t type_table[] = +{ + { "integer", QSE_RAD_ATTR_TYPE_INTEGER }, + { "string", QSE_RAD_ATTR_TYPE_STRING }, + { "ipaddr", QSE_RAD_ATTR_TYPE_IPADDR }, + { "date", QSE_RAD_ATTR_TYPE_DATE }, + { "abinary", QSE_RAD_ATTR_TYPE_ABINARY }, + { "octets", QSE_RAD_ATTR_TYPE_OCTETS }, + { "ifid", QSE_RAD_ATTR_TYPE_IFID }, + { "ipv6addr", QSE_RAD_ATTR_TYPE_IPV6ADDR }, + { "ipv6prefix", QSE_RAD_ATTR_TYPE_IPV6PREFIX }, + { "byte", QSE_RAD_ATTR_TYPE_BYTE }, + { "short", QSE_RAD_ATTR_TYPE_SHORT }, + { "ether", QSE_RAD_ATTR_TYPE_ETHERNET }, + { "combo-ip", QSE_RAD_ATTR_TYPE_COMBO_IP }, + { "tlv", QSE_RAD_ATTR_TYPE_TLV }, + { "signed", QSE_RAD_ATTR_TYPE_SIGNED }, + { QSE_NULL, 0 } +}; + +int qse_raddic_init (qse_raddic_t* dic, qse_mmgr_t* mmgr) +{ + QSE_MEMSET (dic, 0, QSE_SIZEOF(*dic)); + dic->mmgr = mmgr; + + qse_rbt_init (&dic->vendors_byname, mmgr, 1, 1); + qse_rbt_init (&dic->vendors_byvalue, mmgr, 1, 1); + qse_rbt_init (&dic->attributes_byname, mmgr, 1, 1); + qse_rbt_init (&dic->attributes_byvalue, mmgr, 1, 1); + qse_rbt_init (&dic->values_byname, mmgr, 1, 1); + qse_rbt_init (&dic->values_byvalue, mmgr, 1, 1); + + return 0; +} + +void qse_raddic_fini (qse_raddic_t* dic) +{ + qse_rbt_fini (&dic->vendors_byname); + qse_rbt_fini (&dic->vendors_byvalue); + qse_rbt_fini (&dic->attributes_byname); + qse_rbt_fini (&dic->attributes_byvalue); + qse_rbt_fini (&dic->values_byvalue); + qse_rbt_fini (&dic->values_byname); +} + + +qse_raddic_t* qse_raddic_open (qse_mmgr_t* mmgr, qse_size_t xtnsize) +{ + qse_raddic_t* dic; + + dic = (qse_raddic_t*) QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_raddic_t) + xtnsize); + if (dic) + { + if (qse_raddic_init (dic, mmgr) <= -1) + { + QSE_MMGR_FREE (mmgr, dic); + return QSE_NULL; + } + else QSE_MEMSET (QSE_XTN(dic), 0, xtnsize); + } + + return dic; +} + +void qse_raddic_close (qse_raddic_t* dic) +{ + qse_raddic_fini (dic); + QSE_MMGR_FREE (dic->mmgr, dic); + +} + +static int str2argv (qse_mchar_t *str, qse_mchar_t* argv[], int max_argc) +{ + int nflds, i; + qse_mchar_t* ptr; + + nflds = qse_mbsspl (str, QSE_T(""), QSE_MT('\0'), QSE_MT('\0'), QSE_MT('\0')); + if (nflds <= 0) + { + return -1; + } + + ptr = str; + for (i = 0; i < nflds; i++) + { + argv[i] = ptr; + while (*ptr != QSE_MT('\0')) ptr++; + ptr++; + } + + return nflds; +} + + + +#if 0 +static int process_value(const char* fn, qse_size_t line, qse_mchar_t* argv[], int argc) +{ + /* Process the VALUE command */ + + int value; + + if (argc != 3) + { + fr_strerror_printf("dict_init: %s[%d]: invalid VALUE line", fn, line); + return -1; + } + + /* For Compatibility, skip "Server-Config" */ + if (qse_mbscasecmp(argv[0], "Server-Config") == 0) return 0; + + /* Validate all entries */ + if (!sscanf_i(argv[2], &value)) + { + fr_strerror_printf("dict_init: %s[%d]: invalid value", fn, line); + return -1; + } + + if (dict_addvalue(argv[1], argv[0], value) < 0) + { + fr_strerror_printf("dict_init: %s[%d]: %s", fn, line, fr_strerror()); + return -1; + } + + return 0; +} + +static int load_file (qse_raddic_t* dic, const char *dir, const char *fn, const char *src_file, int src_line) +{ + qse_sio_t* sio = QSE_NULL; + char dirtmp[256]; + char buf[256]; + char* p; + qse_size_t line = 0; + int vendor; + int block_vendor; +#if 0 + struct stat statbuf; +#endif + qse_mchar_t* argv[16]; /* TODO: what is the best size? */ + int argc; + qse_raddic_attr_t* da, * block_tlv = QSE_NULL; + +#if 0 + if (qse_mbslen(fn) >= sizeof(dirtmp) / 2 || + qse_mbslen(dir) >= sizeof(dirtmp) / 2) + { + fr_strerror_printf("dict_init: filename name too long"); + return -1; + } + + /* + * First see if fn is relative to dir. If so, create + * new filename. If not, remember the absolute dir. + */ + if ((p = strrchr(fn, FR_DIR_SEP)) != NULL) + { + qse_mbscpy(dirtmp, fn); + dirtmp[p - fn] = 0; + dir = dirtmp; + } + else if (dir && dir[0] && qse_mbscmp(dir, ".") != 0) + { + snprintf(dirtmp, sizeof(dirtmp), "%s/%s", dir, fn); + fn = dirtmp; + } +#endif + + sio = qse_sio_open (dic->mmgr, 0, fn, QSE_SIO_READ); + if (!sio) + { +#if 0 + if (!src_file) { + fr_strerror_printf("dict_init: Couldn't open dictionary \"%s\": %s", + fn, strerror(errno)); + } else { + fr_strerror_printf("dict_init: %s[%d]: Couldn't open dictionary \"%s\": %s", + src_file, src_line, fn, strerror(errno)); + } + } +#endif + return -1; + } + +#if 0 + stat(fn, &statbuf); /* fopen() guarantees this will succeed */ + if (!S_ISREG(statbuf.st_mode)) { + fclose(fp); + fr_strerror_printf("dict_init: Dictionary \"%s\" is not a regular file", + fn); + return -1; + } + + dict_stat_add(fn, &statbuf); + + /* + * Seed the random pool with data. + */ + fr_rand_seed(&statbuf, sizeof(statbuf)); +#endif + + block_vendor = 0; + + while (qse_sio_getmbs (sio, buf, QSE_COUNTOF(buf)) >= 0) + { + line++; + + qse_mbspac (buf); + if (buf[0] == QSE_MT('\0') || buf[0] == QSE_MT('#')) continue; + + /* + * Comment characters should NOT be appearing anywhere but + * as start of a comment; + */ + p = qse_mbschr (buf, QSE_MT('#')); + if (p) *p = '\0'; + + argc = str2argv(buf, argv, QSE_COUNTOF(argv)); + if (argc == 0) continue; + + if (argc == 1) + { + fr_strerror_printf( "dict_init: %s[%d] invalid entry", fn, line); + goto oops; + } + + /* + * Process VALUE lines. + */ + if (qse_mbscasecmp(argv[0], "VALUE") == 0) + { + if (process_value(fn, line, argv + 1, argc - 1) == -1) goto oops; + continue; + } + + /* + * Perhaps this is an attribute. + */ + if (qse_mbscasecmp(argv[0], "ATTRIBUTE") == 0) + { + if (process_attribute(fn, line, block_vendor, block_tlv, argv + 1, argc - 1) == -1) goto oops; + continue; + } + + /* + * See if we need to import another dictionary. + */ + if (qse_mbscasecmp(argv[0], "$INCLUDE") == 0) + { + if (my_dict_init(dir, argv[1], fn, line) < 0) goto oops; + continue; + } /* $INCLUDE */ + + if (qse_mbscasecmp(argv[0], "VALUE-ALIAS") == 0) + { + if (process_value_alias(fn, line, argv + 1, argc - 1) == -1) goto oops; + continue; + } + + /* + * Process VENDOR lines. + */ + if (qse_mbscasecmp(argv[0], "VENDOR") == 0) + { + if (process_vendor(fn, line, argv + 1, argc - 1) == -1) goto oops; + continue; + } + + if (qse_mbscasecmp(argv[0], "BEGIN-TLV") == 0) + { + if (argc != 2) + { + fr_strerror_printf("dict_init: %s[%d] invalid BEGIN-TLV entry", fn, line); + goto oops; + } + + da = dict_attrbyname(argv[1]); + if (!da) + { + fr_strerror_printf("dict_init: %s[%d]: unknown attribute %s", fn, line, argv[1]); + goto oops; + } + + if (da->type != QSE_RAD_ATTR_TYPE_TLV) + { + fr_strerror_printf("dict_init: %s[%d]: attribute %s is not of type tlv", fn, line, argv[1]); + goto oops; + } + + block_tlv = da; + continue; + } /* BEGIN-TLV */ + + if (qse_mbscasecmp(argv[0], "END-TLV") == 0) + { + if (argc != 2) + { + fr_strerror_printf("dict_init: %s[%d] invalid END-TLV entry", fn, line); + goto oops; + } + + da = dict_attrbyname(argv[1]); + if (!da) + { + fr_strerror_printf("dict_init: %s[%d]: unknown attribute %s", fn, line, argv[1]); + goto oops; + } + + if (da != block_tlv) + { + fr_strerror_printf("dict_init: %s[%d]: END-TLV %s does not match any previous BEGIN-TLV", fn, line, argv[1]); + goto oops; + } + block_tlv = NULL; + continue; + } /* END-VENDOR */ + + if (qse_mbscasecmp(argv[0], "BEGIN-VENDOR") == 0) { + if (argc != 2) { + fr_strerror_printf("dict_init: %s[%d] invalid BEGIN-VENDOR entry", fn, line); + goto oops; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) { + fr_strerror_printf("dict_init: %s[%d]: unknown vendor %s", fn, line, argv[1]); + goto oops; + } + block_vendor = vendor; + continue; + } /* BEGIN-VENDOR */ + + if (qse_mbscasecmp(argv[0], "END-VENDOR") == 0) + { + if (argc != 2) { + fr_strerror_printf("dict_init: %s[%d] invalid END-VENDOR entry", fn, line); + goto oops; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) + { + fr_strerror_printf("dict_init: %s[%d]: unknown vendor %s", fn, line, argv[1]); + goto oops; + } + + if (vendor != block_vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: END-VENDOR %s does not match any previous BEGIN-VENDOR", + fn, line, argv[1]); + goto oops; + } + block_vendor = 0; + continue; + } /* END-VENDOR */ + + /* + * Any other string: We don't recognize it. + */ + fr_strerror_printf("dict_init: %s[%d] invalid keyword \"%s\"", fn, line, argv[0]); + goto oops; + + } + + qse_sio_close (sio); + return 0; + + +oops: + if (sio) qse_sio_close (sio); + return -1; +} + + +#endif + + + + + + + + + + + + + + + + + + + + +#if 0 +#define DICT_VALUE_MAX_NAME_LEN (128) +#define DICT_VENDOR_MAX_NAME_LEN (128) +#define DICT_ATTR_MAX_NAME_LEN (128) + +static fr_hash_table_t *vendors_byname = NULL; +static fr_hash_table_t *vendors_byvalue = NULL; + +static fr_hash_table_t *attributes_byname = NULL; +static fr_hash_table_t *attributes_byvalue = NULL; + +static fr_hash_table_t *values_byvalue = NULL; +static fr_hash_table_t *values_byname = NULL; + +static DICT_ATTR *dict_base_attrs[256]; + +/* + * For faster HUP's, we cache the stat information for + * files we've $INCLUDEd + */ +typedef struct dict_stat_t { + struct dict_stat_t *next; + char *name; + time_t mtime; +} dict_stat_t; + +static char *stat_root_dir = NULL; +static char *stat_root_file = NULL; + +static dict_stat_t *stat_head = NULL; +static dict_stat_t *stat_tail = NULL; + +typedef struct value_fixup_t { + char attrstr[DICT_ATTR_MAX_NAME_LEN]; + DICT_VALUE *dval; + struct value_fixup_t *next; +} value_fixup_t; + + +/* + * So VALUEs in the dictionary can have forward references. + */ +static value_fixup_t *value_fixup = NULL; + +static const FR_NAME_NUMBER type_table[] = { + { "integer", QSE_RAD_ATTR_TYPE_INTEGER }, + { "string", QSE_RAD_ATTR_TYPE_STRING }, + { "ipaddr", QSE_RAD_ATTR_TYPE_IPADDR }, + { "date", QSE_RAD_ATTR_TYPE_DATE }, + { "abinary", QSE_RAD_ATTR_TYPE_ABINARY }, + { "octets", QSE_RAD_ATTR_TYPE_OCTETS }, + { "ifid", QSE_RAD_ATTR_TYPE_IFID }, + { "ipv6addr", QSE_RAD_ATTR_TYPE_IPV6ADDR }, + { "ipv6prefix", QSE_RAD_ATTR_TYPE_IPV6PREFIX }, + { "byte", QSE_RAD_ATTR_TYPE_BYTE }, + { "short", QSE_RAD_ATTR_TYPE_SHORT }, + { "ether", QSE_RAD_ATTR_TYPE_ETHERNET }, + { "combo-ip", QSE_RAD_ATTR_TYPE_COMBO_IP }, + { "tlv", QSE_RAD_ATTR_TYPE_TLV }, + { "signed", QSE_RAD_ATTR_TYPE_SIGNED }, + { NULL, 0 } +}; + + +/* + * Create the hash of the name. + * + * We copy the hash function here because it's substantially faster. + */ +#define FNV_MAGIC_INIT (0x811c9dc5) +#define FNV_MAGIC_PRIME (0x01000193) + +static uint32_t dict_hashname(const char *name) +{ + uint32_t hash = FNV_MAGIC_INIT; + const char *p; + + for (p = name; *p != '\0'; p++) { + int c = *(const unsigned char *) p; + if (isalpha(c)) c = tolower(c); + + hash *= FNV_MAGIC_PRIME; + hash ^= (uint32_t ) (c & 0xff); + } + + return hash; +} + + +/* + * Hash callback functions. + */ +static uint32_t dict_attr_name_hash(const void *data) +{ + return dict_hashname(((const DICT_ATTR *)data)->name); +} + +static int dict_attr_name_cmp(const void *one, const void *two) +{ + const DICT_ATTR *a = one; + const DICT_ATTR *b = two; + + return qse_mbscasecmp(a->name, b->name); +} + +static uint32_t dict_attr_value_hash(const void *data) +{ + uint32_t hash; + const DICT_ATTR *attr = data; + + hash = fr_hash(&attr->vendor, sizeof(attr->vendor)); + return fr_hash_update(&attr->attr, sizeof(attr->attr), hash); +} + +static int dict_attr_value_cmp(const void *one, const void *two) +{ + const DICT_ATTR *a = one; + const DICT_ATTR *b = two; + + if (a->vendor < b->vendor) return -1; + if (a->vendor > b->vendor) return +1; + + return a->attr - b->attr; +} + +static uint32_t dict_vendor_name_hash(const void *data) +{ + return dict_hashname(((const DICT_VENDOR *)data)->name); +} + +static int dict_vendor_name_cmp(const void *one, const void *two) +{ + const DICT_VENDOR *a = one; + const DICT_VENDOR *b = two; + + return qse_mbscasecmp(a->name, b->name); +} + +static uint32_t dict_vendor_value_hash(const void *data) +{ + return fr_hash(&(((const DICT_VENDOR *)data)->vendorpec), + sizeof(((const DICT_VENDOR *)data)->vendorpec)); +} + +static int dict_vendor_value_cmp(const void *one, const void *two) +{ + const DICT_VENDOR *a = one; + const DICT_VENDOR *b = two; + + return a->vendorpec - b->vendorpec; +} + +static uint32_t dict_value_name_hash(const void *data) +{ + uint32_t hash; + const DICT_VALUE *dval = data; + + hash = dict_hashname(dval->name); + return fr_hash_update(&dval->attr, sizeof(dval->attr), hash); +} + +static int dict_value_name_cmp(const void *one, const void *two) +{ + int rcode; + const DICT_VALUE *a = one; + const DICT_VALUE *b = two; + + rcode = a->attr - b->attr; + if (rcode != 0) return rcode; + + return qse_mbscasecmp(a->name, b->name); +} + +static uint32_t dict_value_value_hash(const void *data) +{ + uint32_t hash; + const DICT_VALUE *dval = data; + + hash = fr_hash(&dval->attr, sizeof(dval->attr)); + return fr_hash_update(&dval->value, sizeof(dval->value), hash); +} + +static int dict_value_value_cmp(const void *one, const void *two) +{ + int rcode; + const DICT_VALUE *a = one; + const DICT_VALUE *b = two; + + rcode = a->attr - b->attr; + if (rcode != 0) return rcode; + + return a->value - b->value; +} + + +/* + * Free the list of stat buffers + */ +static void dict_stat_free(void) +{ + dict_stat_t *this, *next; + + free(stat_root_dir); + stat_root_dir = NULL; + free(stat_root_file); + stat_root_file = NULL; + + if (!stat_head) { + stat_tail = NULL; + return; + } + + for (this = stat_head; this != NULL; this = next) { + next = this->next; + free(this->name); + free(this); + } + + stat_head = stat_tail = NULL; +} + + +/* + * Add an entry to the list of stat buffers. + */ +static void dict_stat_add(const char *name, const struct stat *stat_buf) +{ + dict_stat_t *this; + + this = malloc(sizeof(*this)); + if (!this) return; + QSE_MEMSET(this, 0, sizeof(*this)); + + this->name = strdup(name); + this->mtime = stat_buf->st_mtime; + + if (!stat_head) { + stat_head = stat_tail = this; + } else { + stat_tail->next = this; + stat_tail = this; + } +} + + +/* + * See if any dictionaries have changed. If not, don't + * do anything. + */ +static int dict_stat_check(const char *root_dir, const char *root_file) +{ + struct stat buf; + dict_stat_t *this; + + if (!stat_root_dir) return 0; + if (!stat_root_file) return 0; + + if (qse_mbscmp(root_dir, stat_root_dir) != 0) return 0; + if (qse_mbscmp(root_file, stat_root_file) != 0) return 0; + + if (!stat_head) return 0; /* changed, reload */ + + for (this = stat_head; this != NULL; this = this->next) { + if (stat(this->name, &buf) < 0) return 0; + + if (buf.st_mtime != this->mtime) return 0; + } + + return 1; +} + +typedef struct fr_pool_t { + void *page_end; + void *free_ptr; + struct fr_pool_t *page_free; + struct fr_pool_t *page_next; +} fr_pool_t; + +#define FR_POOL_SIZE (32768) +#define FR_ALLOC_ALIGN (8) + +static fr_pool_t *dict_pool = NULL; + +static fr_pool_t *fr_pool_create(void) +{ + fr_pool_t *fp = malloc(FR_POOL_SIZE); + + if (!fp) return NULL; + + QSE_MEMSET(fp, 0, FR_POOL_SIZE); + + fp->page_end = ((uint8_t *) fp) + FR_POOL_SIZE; + fp->free_ptr = ((uint8_t *) fp) + sizeof(*fp); + fp->page_free = fp; + fp->page_next = NULL; + return fp; +} + +static void fr_pool_delete(fr_pool_t **pfp) +{ + fr_pool_t *fp, *next; + + if (!pfp || !*pfp) return; + + for (fp = *pfp; fp != NULL; fp = next) { + next = fp->page_next; + free(fp); + } +} + + +static void *fr_pool_alloc(size_t size) +{ + void *ptr; + + if (size == 0) return NULL; + + if (size > 256) return NULL; /* shouldn't happen */ + + if (!dict_pool) { + dict_pool = fr_pool_create(); + if (!dict_pool) return NULL; + } + + if ((size & (FR_ALLOC_ALIGN - 1)) != 0) { + size += FR_ALLOC_ALIGN - (size & (FR_ALLOC_ALIGN - 1)); + } + + if ((((uint8_t *) dict_pool->page_free->free_ptr) + size) > (uint8_t *) dict_pool->page_free->page_end) { + dict_pool->page_free->page_next = fr_pool_create(); + if (!dict_pool->page_free->page_next) return NULL; + dict_pool->page_free = dict_pool->page_free->page_next; + } + + ptr = dict_pool->page_free->free_ptr; + dict_pool->page_free->free_ptr = ((uint8_t *) dict_pool->page_free->free_ptr) + size; + + return ptr; +} + + +static void fr_pool_free(UNUSED void *ptr) +{ + /* + * Place-holder for later code. + */ +} + +/* + * Free the dictionary_attributes and dictionary_values lists. + */ +void dict_free(void) +{ + /* + * Free the tables + */ + fr_hash_table_free(vendors_byname); + fr_hash_table_free(vendors_byvalue); + vendors_byname = NULL; + vendors_byvalue = NULL; + + fr_hash_table_free(attributes_byname); + fr_hash_table_free(attributes_byvalue); + attributes_byname = NULL; + attributes_byvalue = NULL; + + fr_hash_table_free(values_byname); + fr_hash_table_free(values_byvalue); + values_byname = NULL; + values_byvalue = NULL; + + QSE_MEMSET(dict_base_attrs, 0, sizeof(dict_base_attrs)); + + fr_pool_delete(&dict_pool); + + dict_stat_free(); +} + + +/* + * Add vendor to the list. + */ +int dict_addvendor(const char *name, int value) +{ + size_t length; + DICT_VENDOR *dv; + + /* BACON */ + /*if (value >= 32767) {*/ + if (value >= 65535) { + /* END BACON */ + fr_strerror_printf("dict_addvendor: Cannot handle vendor ID larger than 65535"); + return -1; + } + + if ((length = qse_mbslen(name)) >= DICT_VENDOR_MAX_NAME_LEN) { + fr_strerror_printf("dict_addvendor: vendor name too long"); + return -1; + } + + if ((dv = fr_pool_alloc(sizeof(*dv) + length)) == NULL) { + fr_strerror_printf("dict_addvendor: out of memory"); + return -1; + } + + qse_mbscpy(dv->name, name); + dv->vendorpec = value; + dv->type = dv->length = 1; /* defaults */ + + if (!fr_hash_table_insert(vendors_byname, dv)) { + DICT_VENDOR *old_dv; + + old_dv = fr_hash_table_finddata(vendors_byname, dv); + if (!old_dv) { + fr_strerror_printf("dict_addvendor: Failed inserting vendor name %s", name); + return -1; + } + if (old_dv->vendorpec != dv->vendorpec) { + fr_strerror_printf("dict_addvendor: Duplicate vendor name %s", name); + return -1; + } + + /* + * Already inserted. Discard the duplicate entry. + */ + fr_pool_free(dv); + return 0; + } + + /* + * Insert the SAME pointer (not free'd when this table is + * deleted), into another table. + * + * We want this behaviour because we want OLD names for + * the attributes to be read from the configuration + * files, but when we're printing them, (and looking up + * by value) we want to use the NEW name. + */ + if (!fr_hash_table_replace(vendors_byvalue, dv)) { + fr_strerror_printf("dict_addvendor: Failed inserting vendor %s", + name); + return -1; + } + + return 0; +} + +/* + * Add an attribute to the dictionary. + */ +int dict_addattr(const char *name, int vendor, int type, int value, + ATTR_FLAGS flags) +{ + size_t namelen; + static int max_attr = 0; + DICT_ATTR *attr; + + namelen = qse_mbslen(name); + if (namelen >= DICT_ATTR_MAX_NAME_LEN) { + fr_strerror_printf("dict_addattr: attribute name too long"); + return -1; + } + + /* + * If the value is '-1', that means use a pre-existing + * one (if it already exists). If one does NOT already exist, + * then create a new attribute, with a non-conflicting value, + * and use that. + */ + if (value == -1) { + if (dict_attrbyname(name)) { + return 0; /* exists, don't add it again */ + } + + value = ++max_attr; + + } else if (vendor == 0) { + /* + * Update 'max_attr' + */ + if (value > max_attr) { + max_attr = value; + } + } + + if (value < 0) { + fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (less than zero)"); + return -1; + } + + if (value >= 65536) { + fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (larger than 65535)."); + return -1; + } + + if (vendor) { + DICT_VENDOR *dv; + static DICT_VENDOR *last_vendor = NULL; + + if (flags.is_tlv && (flags.encrypt != FLAG_ENCRYPT_NONE)) { + fr_strerror_printf("Sub-TLV's cannot be encrypted"); + return -1; + } + + if (flags.has_tlv && (flags.encrypt != FLAG_ENCRYPT_NONE)) { + fr_strerror_printf("TLV's cannot be encrypted"); + return -1; + } + + if (flags.is_tlv && flags.has_tag) { + fr_strerror_printf("Sub-TLV's cannot have a tag"); + return -1; + } + + if (flags.has_tlv && flags.has_tag) { + fr_strerror_printf("TLV's cannot have a tag"); + return -1; + } + + /* + * Most ATTRIBUTEs are bunched together by + * VENDOR. We can save a lot of lookups on + * dictionary initialization by caching the last + * vendor. + */ + if (last_vendor && (vendor == last_vendor->vendorpec)) { + dv = last_vendor; + } else { + dv = dict_vendorbyvalue(vendor); + last_vendor = dv; + } + + /* + * If the vendor isn't defined, die. + */ + if (!dv) { + fr_strerror_printf("dict_addattr: Unknown vendor"); + return -1; + } + + /* + * FIXME: Switch over dv->type, and limit things + * properly. + */ + if ((dv->type == 1) && (value >= 256) && !flags.is_tlv) { + fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (larger than 255)."); + return -1; + } /* else 256..65535 are allowed */ + } + + /* + * Create a new attribute for the list + */ + if ((attr = fr_pool_alloc(sizeof(*attr) + namelen)) == NULL) { + fr_strerror_printf("dict_addattr: out of memory"); + return -1; + } + + memcpy(attr->name, name, namelen); + attr->name[namelen] = '\0'; + attr->attr = value; + attr->attr |= (vendor << 16); /* FIXME: hack */ + attr->vendor = vendor; + attr->type = type; + attr->flags = flags; + attr->vendor = vendor; + + /* + * Insert the attribute, only if it's not a duplicate. + */ + if (!fr_hash_table_insert(attributes_byname, attr)) { + DICT_ATTR *a; + + /* + * If the attribute has identical number, then + * ignore the duplicate. + */ + a = fr_hash_table_finddata(attributes_byname, attr); + if (a && (qse_mbscasecmp(a->name, attr->name) == 0)) { + if (a->attr != attr->attr) { + fr_strerror_printf("dict_addattr: Duplicate attribute name %s", name); + fr_pool_free(attr); + return -1; + } + + /* + * Same name, same vendor, same attr, + * maybe the flags and/or type is + * different. Let the new value + * over-ride the old one. + */ + } + + + fr_hash_table_delete(attributes_byvalue, a); + + if (!fr_hash_table_replace(attributes_byname, attr)) { + fr_strerror_printf("dict_addattr: Internal error storing attribute %s", name); + fr_pool_free(attr); + return -1; + } + } + + /* + * Insert the SAME pointer (not free'd when this entry is + * deleted), into another table. + * + * We want this behaviour because we want OLD names for + * the attributes to be read from the configuration + * files, but when we're printing them, (and looking up + * by value) we want to use the NEW name. + */ + if (!fr_hash_table_replace(attributes_byvalue, attr)) { + fr_strerror_printf("dict_addattr: Failed inserting attribute name %s", name); + return -1; + } + + if (!vendor && (value > 0) && (value < 256)) { + dict_base_attrs[value] = attr; + } + + return 0; +} + + +/* + * Add a value for an attribute to the dictionary. + */ +int dict_addvalue(const char *namestr, const char *attrstr, int value) +{ + qse_size_t length; + DICT_ATTR* dattr; + DICT_VALUE* dval; + + static DICT_ATTR *last_attr = NULL; + + if (!*namestr) + { + fr_strerror_printf("dict_addvalue: empty names are not permitted"); + return -1; + } + + if ((length = qse_mbslen(namestr)) >= DICT_VALUE_MAX_NAME_LEN) + { + fr_strerror_printf("dict_addvalue: value name too long"); + return -1; + } + + if ((dval = fr_pool_alloc(sizeof(*dval) + length)) == NULL) + { + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + QSE_MEMSET(dval, 0, sizeof(*dval)); + + qse_mbscpy(dval->name, namestr); + dval->value = value; + + /* + * Most VALUEs are bunched together by ATTRIBUTE. We can + * save a lot of lookups on dictionary initialization by + * caching the last attribute. + */ + if (last_attr && (qse_mbscasecmp(attrstr, last_attr->name) == 0)) { + dattr = last_attr; + } else { + dattr = dict_attrbyname(attrstr); + last_attr = dattr; + } + + /* + * Remember which attribute is associated with this + * value, if possible. + */ + if (dattr) + { + if (dattr->flags.has_value_alias) + { + fr_strerror_printf("dict_addvalue: Cannot add VALUE for ATTRIBUTE \"%s\": It already has a VALUE-ALIAS", attrstr); + return -1; + } + + dval->attr = dattr->attr; + + /* + * Enforce valid values + * + * Don't worry about fixups... + */ + switch (dattr->type) + { + case QSE_RAD_ATTR_TYPE_BYTE: + if (value > 255) + { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: ATTRIBUTEs of type 'byte' cannot have VALUEs larger than 255"); + return -1; + } + break; + case QSE_RAD_ATTR_TYPE_SHORT: + if (value > 65535) + { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: ATTRIBUTEs of type 'short' cannot have VALUEs larger than 65535"); + return -1; + } + break; + + /* + * Allow octets for now, because + * of dictionary.cablelabs + */ + case QSE_RAD_ATTR_TYPE_OCTETS: + case QSE_RAD_ATTR_TYPE_INTEGER: + break; + + default: + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: VALUEs cannot be defined for attributes of type '%s'", + fr_int2str(type_table, dattr->type, "?Unknown?")); + return -1; + } + + dattr->flags.has_value = 1; + } + else + { + value_fixup_t *fixup; + + fixup = (value_fixup_t *) malloc(sizeof(*fixup)); + if (!fixup) { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + QSE_MEMSET(fixup, 0, sizeof(*fixup)); + + strlcpy(fixup->attrstr, attrstr, sizeof(fixup->attrstr)); + fixup->dval = dval; + + /* + * Insert to the head of the list. + */ + fixup->next = value_fixup; + value_fixup = fixup; + + return 0; + } + + /* + * Add the value into the dictionary. + */ + if (!fr_hash_table_insert(values_byname, dval)) + { + if (dattr) + { + DICT_VALUE *old; + + /* + * Suppress duplicates with the same + * name and value. There are lots in + * dictionary.ascend. + */ + old = dict_valbyname(dattr->attr, namestr); + if (old && (old->value == dval->value)) + { + fr_pool_free(dval); + return 0; + } + } + + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: Duplicate value name %s for attribute %s", namestr, attrstr); + return -1; + } + + /* + * There are multiple VALUE's, keyed by attribute, so we + * take care of that here. + */ + if (!fr_hash_table_replace(values_byvalue, dval)) + { + fr_strerror_printf("dict_addvalue: Failed inserting value %s", namestr); + return -1; + } + + return 0; +} + +static int sscanf_i(const char *str, int *pvalue) +{ + int rcode = 0; + int base = 10; + const char *tab = "0123456789"; + + if ((str[0] == '0') && + ((str[1] == 'x') || (str[1] == 'X'))) { + tab = "0123456789abcdef"; + base = 16; + + str += 2; + } + + while (*str) { + const char *c; + + c = memchr(tab, tolower((int) *str), base); + if (!c) return 0; + + rcode *= base; + rcode += (c - tab); + str++; + } + + *pvalue = rcode; + return 1; +} + + +/* + * Process the ATTRIBUTE command + */ +static int process_attribute ( + const char* fn, const qse_size_t line, + const int block_vendor, DICT_ATTR *block_tlv, + char **argv, int argc) +{ + int vendor = 0; + int value; + int type; + ATTR_FLAGS flags; + + if ((argc < 3) || (argc > 4)) { + fr_strerror_printf("dict_init: %s[%d]: invalid ATTRIBUTE line", + fn, line); + return -1; + } + + /* + * Validate all entries + */ + if (!sscanf_i(argv[1], &value)) + { + fr_strerror_printf("dict_init: %s[%d]: invalid value", fn, line); + return -1; + } + + /* + * find the type of the attribute. + */ + type = fr_str2int(type_table, argv[2], -1); + if (type < 0) { + fr_strerror_printf("dict_init: %s[%d]: invalid type \"%s\"", + fn, line, argv[2]); + return -1; + } + + /* + * Only look up the vendor if the string + * is non-empty. + */ + QSE_MEMSET (&flags, 0, sizeof(flags)); + if (argc == 4) + { + char *key, *next, *last; + + key = argv[3]; + do { + next = strchr(key, ','); + if (next) *(next++) = '\0'; + + if (qse_mbscmp(key, "has_tag") == 0 || + qse_mbscmp(key, "has_tag=1") == 0) { + /* Boolean flag, means this is a + tagged attribute */ + flags.has_tag = 1; + + } else if (strncmp(key, "encrypt=", 8) == 0) { + /* Encryption method, defaults to 0 (none). + Currently valid is just type 2, + Tunnel-Password style, which can only + be applied to strings. */ + flags.encrypt = strtol(key + 8, &last, 0); + if (*last) { + fr_strerror_printf( "dict_init: %s[%d] invalid option %s", + fn, line, key); + return -1; + } + + } else if (strncmp(key, "array", 8) == 0) { + flags.array = 1; + + switch (type) { + case QSE_RAD_ATTR_TYPE_IPADDR: + case QSE_RAD_ATTR_TYPE_BYTE: + case QSE_RAD_ATTR_TYPE_SHORT: + case QSE_RAD_ATTR_TYPE_INTEGER: + case QSE_RAD_ATTR_TYPE_DATE: + break; + + default: + fr_strerror_printf( "dict_init: %s[%d] Only IP addresses can have the \"array\" flag set.", + fn, line); + return -1; + } + + } else { + fr_strerror_printf( "dict_init: %s[%d]: unknown option \"%s\"", + fn, line, key); + return -1; + } + + key = next; + if (key && !*key) break; + } while (key); + } + + if (block_vendor) vendor = block_vendor; + + /* + * Special checks for tags, they make our life much more + * difficult. + */ + if (flags.has_tag) { + /* + * Only string, octets, and integer can be tagged. + */ + switch (type) { + case QSE_RAD_ATTR_TYPE_STRING: + case QSE_RAD_ATTR_TYPE_INTEGER: + break; + + default: + fr_strerror_printf("dict_init: %s[%d]: Attributes of type %s cannot be tagged.", + fn, line, + fr_int2str(type_table, type, "?Unknown?")); + return -1; + + } + } + + if (type == QSE_RAD_ATTR_TYPE_TLV) { + flags.has_tlv = 1; + } + + if (block_tlv) { + /* + * TLV's can be only one octet. + */ + if ((value <= 0) || (value > 255)) { + fr_strerror_printf( "dict_init: %s[%d]: sub-tlv's cannot have value > 255", + fn, line); + return -1; + } + + if (flags.encrypt != FLAG_ENCRYPT_NONE) { + fr_strerror_printf( "dict_init: %s[%d]: sub-tlv's cannot be encrypted", + fn, line); + return -1; + } + + /* + * + */ + value <<= 8; + value |= (block_tlv->attr & 0xffff); + flags.is_tlv = 1; + } + + /* + * Add it in. + */ + if (dict_addattr(argv[0], vendor, type, value, flags) < 0) { + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, fr_strerror()); + return -1; + } + + return 0; +} + + +/* + * Process the VALUE command + */ +static int process_value(const char* fn, const qse_size_t line, char **argv, + int argc) +{ + int value; + + if (argc != 3) { + fr_strerror_printf("dict_init: %s[%d]: invalid VALUE line", + fn, line); + return -1; + } + /* + * For Compatibility, skip "Server-Config" + */ + if (qse_mbscasecmp(argv[0], "Server-Config") == 0) + return 0; + + /* + * Validate all entries + */ + if (!sscanf_i(argv[2], &value)) { + fr_strerror_printf("dict_init: %s[%d]: invalid value", + fn, line); + return -1; + } + + if (dict_addvalue(argv[1], argv[0], value) < 0) { + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, fr_strerror()); + return -1; + } + + return 0; +} + + +/* + * Process the VALUE-ALIAS command + * + * This allows VALUE mappings to be shared among multiple + * attributes. + */ +static int process_value_alias(const char* fn, const qse_size_t line, char **argv, + int argc) +{ + DICT_ATTR *my_da, *da; + DICT_VALUE *dval; + + if (argc != 2) { + fr_strerror_printf("dict_init: %s[%d]: invalid VALUE-ALIAS line", + fn, line); + return -1; + } + + my_da = dict_attrbyname(argv[0]); + if (!my_da) { + fr_strerror_printf("dict_init: %s[%d]: ATTRIBUTE \"%s\" does not exist", + fn, line, argv[1]); + return -1; + } + + if (my_da->flags.has_value) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS to ATTRIBUTE \"%s\" with pre-existing VALUE", + fn, line, argv[0]); + return -1; + } + + if (my_da->flags.has_value_alias) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS to ATTRIBUTE \"%s\" with pre-existing VALUE-ALIAS", + fn, line, argv[0]); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf("dict_init: %s[%d]: Cannot find ATTRIBUTE \"%s\" for alias", + fn, line, argv[1]); + return -1; + } + + if (!da->flags.has_value) { + fr_strerror_printf("dict_init: %s[%d]: VALUE-ALIAS cannot refer to ATTRIBUTE %s: It has no values", + fn, line, argv[1]); + return -1; + } + + if (da->flags.has_value_alias) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS to ATTRIBUTE \"%s\" which itself has a VALUE-ALIAS", + fn, line, argv[1]); + return -1; + } + + if (my_da->type != da->type) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS between attributes of differing type", + fn, line); + return -1; + } + + if ((dval = fr_pool_alloc(sizeof(*dval))) == NULL) { + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + + dval->name[0] = '\0'; /* empty name */ + dval->attr = my_da->attr; + dval->value = da->attr; + + if (!fr_hash_table_insert(values_byname, dval)) { + fr_strerror_printf("dict_init: %s[%d]: Error create alias", + fn, line); + fr_pool_free(dval); + return -1; + } + + return 0; +} + + +/* + * Process the VENDOR command + */ +static int process_vendor(const char* fn, const qse_size_t line, char **argv, + int argc) +{ + int value; + int continuation = 0; + const char *format = NULL; + + if ((argc < 2) || (argc > 3)) { + fr_strerror_printf( "dict_init: %s[%d] invalid VENDOR entry", + fn, line); + return -1; + } + + /* + * Validate all entries + */ + if (!isdigit((int) argv[1][0])) { + fr_strerror_printf("dict_init: %s[%d]: invalid value", + fn, line); + return -1; + } + value = atoi(argv[1]); + + /* Create a new VENDOR entry for the list */ + if (dict_addvendor(argv[0], value) < 0) { + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, fr_strerror()); + return -1; + } + + /* + * Look for a format statement + */ + if (argc == 3) { + format = argv[2]; + + } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */ + format = "format=4,0"; + + } else if (value == VENDORPEC_LUCENT) { + format = "format=2,1"; + + } else if (value == VENDORPEC_STARENT) { + format = "format=2,2"; + + } /* else no fixups to do */ + + if (format) { + int type, length; + const char *p; + DICT_VENDOR *dv; + + if (strncasecmp(format, "format=", 7) != 0) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected \"format=\", got \"%s\"", + fn, line, format); + return -1; + } + + p = format + 7; + if ((qse_mbslen(p) < 3) || + !isdigit((int) p[0]) || + (p[1] != ',') || + !isdigit((int) p[2]) || + (p[3] && (p[3] != ','))) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected text like \"1,1\", got \"%s\"", + fn, line, p); + return -1; + } + + type = (int) (p[0] - '0'); + length = (int) (p[2] - '0'); + + if (p[3] == ',') { + if ((p[4] != 'c') || + (p[5] != '\0')) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected text like \"1,1\", got \"%s\"", + fn, line, p); + return -1; + } + continuation = 1; + } + + dv = dict_vendorbyvalue(value); + if (!dv) { + fr_strerror_printf("dict_init: %s[%d]: Failed adding format for VENDOR", + fn, line); + return -1; + } + + if ((type != 1) && (type != 2) && (type != 4)) { + fr_strerror_printf("dict_init: %s[%d]: invalid type value %d for VENDOR", + fn, line, type); + return -1; + } + + if ((length != 0) && (length != 1) && (length != 2)) { + fr_strerror_printf("dict_init: %s[%d]: invalid length value %d for VENDOR", + fn, line, length); + return -1; + } + + dv->type = type; + dv->length = length; + dv->flags = continuation; + } + + return 0; +} + +/* + * String split routine. Splits an input string IN PLACE + * into pieces, based on spaces. + */ +static int str2argv(char *str, char **argv, int max_argc) +{ + int argc = 0; + + while (*str) { + if (argc >= max_argc) return argc; + + /* + * Chop out comments early. + */ + if (*str == '#') { + *str = '\0'; + break; + } + + while ((*str == ' ') || + (*str == '\t') || + (*str == '\r') || + (*str == '\n')) *(str++) = '\0'; + + if (!*str) return argc; + + argv[argc] = str; + argc++; + + while (*str && + (*str != ' ') && + (*str != '\t') && + (*str != '\r') && + (*str != '\n')) str++; + } + + return argc; +} + +#define MAX_ARGV (16) + +/* + * Initialize the dictionary. + */ +static int my_dict_init(const char *dir, const char *fn, + const char *src_file, int src_line) +{ + FILE *fp; + char dirtmp[256]; + char buf[256]; + char *p; + int line = 0; + int vendor; + int block_vendor; + struct stat statbuf; + char *argv[MAX_ARGV]; + int argc; + DICT_ATTR *da, *block_tlv = NULL; + + if (qse_mbslen(fn) >= sizeof(dirtmp) / 2 || + qse_mbslen(dir) >= sizeof(dirtmp) / 2) { + fr_strerror_printf("dict_init: filename name too long"); + return -1; + } + + /* + * First see if fn is relative to dir. If so, create + * new filename. If not, remember the absolute dir. + */ + if ((p = strrchr(fn, FR_DIR_SEP)) != NULL) { + qse_mbscpy(dirtmp, fn); + dirtmp[p - fn] = 0; + dir = dirtmp; + } else if (dir && dir[0] && qse_mbscmp(dir, ".") != 0) { + snprintf(dirtmp, sizeof(dirtmp), "%s/%s", dir, fn); + fn = dirtmp; + } + + if ((fp = fopen(fn, "r")) == NULL) { + if (!src_file) { + fr_strerror_printf("dict_init: Couldn't open dictionary \"%s\": %s", + fn, strerror(errno)); + } else { + fr_strerror_printf("dict_init: %s[%d]: Couldn't open dictionary \"%s\": %s", + src_file, src_line, fn, strerror(errno)); + } + return -1; + } + + stat(fn, &statbuf); /* fopen() guarantees this will succeed */ + if (!S_ISREG(statbuf.st_mode)) { + fclose(fp); + fr_strerror_printf("dict_init: Dictionary \"%s\" is not a regular file", + fn); + return -1; + } + + /* + * Globally writable dictionaries means that users can control + * the server configuration with little difficulty. + */ +#ifdef S_IWOTH + if ((statbuf.st_mode & S_IWOTH) != 0) { + fclose(fp); + fr_strerror_printf("dict_init: Dictionary \"%s\" is globally writable. Refusing to start due to insecure configuration.", + fn); + return -1; + } +#endif + + dict_stat_add(fn, &statbuf); + + /* + * Seed the random pool with data. + */ + fr_rand_seed(&statbuf, sizeof(statbuf)); + + block_vendor = 0; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + line++; + if (buf[0] == '#' || buf[0] == 0 || + buf[0] == '\n' || buf[0] == '\r') + continue; + + /* + * Comment characters should NOT be appearing anywhere but + * as start of a comment; + */ + p = strchr(buf, '#'); + if (p) *p = '\0'; + + argc = str2argv(buf, argv, MAX_ARGV); + if (argc == 0) continue; + + if (argc == 1) { + fr_strerror_printf( "dict_init: %s[%d] invalid entry", + fn, line); + fclose(fp); + return -1; + } + + /* + * Process VALUE lines. + */ + if (qse_mbscasecmp(argv[0], "VALUE") == 0) { + if (process_value(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * Perhaps this is an attribute. + */ + if (qse_mbscasecmp(argv[0], "ATTRIBUTE") == 0) { + if (process_attribute(fn, line, block_vendor, + block_tlv, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * See if we need to import another dictionary. + */ + if (qse_mbscasecmp(argv[0], "$INCLUDE") == 0) { + if (my_dict_init(dir, argv[1], fn, line) < 0) { + fclose(fp); + return -1; + } + continue; + } /* $INCLUDE */ + + if (qse_mbscasecmp(argv[0], "VALUE-ALIAS") == 0) { + if (process_value_alias(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * Process VENDOR lines. + */ + if (qse_mbscasecmp(argv[0], "VENDOR") == 0) { + if (process_vendor(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + if (qse_mbscasecmp(argv[0], "BEGIN-TLV") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid BEGIN-TLV entry", + fn, line); + fclose(fp); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown attribute %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (da->type != QSE_RAD_ATTR_TYPE_TLV) { + fr_strerror_printf( + "dict_init: %s[%d]: attribute %s is not of type tlv", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + block_tlv = da; + continue; + } /* BEGIN-TLV */ + + if (qse_mbscasecmp(argv[0], "END-TLV") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid END-TLV entry", + fn, line); + fclose(fp); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown attribute %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (da != block_tlv) { + fr_strerror_printf( + "dict_init: %s[%d]: END-TLV %s does not match any previous BEGIN-TLV", + fn, line, argv[1]); + fclose(fp); + return -1; + } + block_tlv = NULL; + continue; + } /* END-VENDOR */ + + if (qse_mbscasecmp(argv[0], "BEGIN-VENDOR") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid BEGIN-VENDOR entry", + fn, line); + fclose(fp); + return -1; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown vendor %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + block_vendor = vendor; + continue; + } /* BEGIN-VENDOR */ + + if (qse_mbscasecmp(argv[0], "END-VENDOR") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid END-VENDOR entry", + fn, line); + fclose(fp); + return -1; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown vendor %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (vendor != block_vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: END-VENDOR %s does not match any previous BEGIN-VENDOR", + fn, line, argv[1]); + fclose(fp); + return -1; + } + block_vendor = 0; + continue; + } /* END-VENDOR */ + + /* + * Any other string: We don't recognize it. + */ + fr_strerror_printf("dict_init: %s[%d] invalid keyword \"%s\"", + fn, line, argv[0]); + fclose(fp); + return -1; + } + fclose(fp); + return 0; +} + + +/* + * Empty callback for hash table initialization. + */ +static int null_callback(void *ctx, void *data) +{ + ctx = ctx; /* -Wunused */ + data = data; /* -Wunused */ + + return 0; +} + + +/* + * Initialize the directory, then fix the attr member of + * all attributes. + */ +int dict_init(const char *dir, const char *fn) +{ + /* + * Check if we need to change anything. If not, don't do + * anything. + */ + if (dict_stat_check(dir, fn)) { + return 0; + } + + /* + * Free the dictionaries, and the stat cache. + */ + dict_free(); + stat_root_dir = strdup(dir); + stat_root_file = strdup(fn); + + /* + * Create the table of vendor by name. There MAY NOT + * be multiple vendors of the same name. + * + * Each vendor is malloc'd, so the free function is free. + */ + vendors_byname = fr_hash_table_create(dict_vendor_name_hash, + dict_vendor_name_cmp, + fr_pool_free); + if (!vendors_byname) { + return -1; + } + + /* + * Create the table of vendors by value. There MAY + * be vendors of the same value. If there are, we + * pick the latest one. + */ + vendors_byvalue = fr_hash_table_create(dict_vendor_value_hash, + dict_vendor_value_cmp, + fr_pool_free); + if (!vendors_byvalue) { + return -1; + } + + /* + * Create the table of attributes by name. There MAY NOT + * be multiple attributes of the same name. + * + * Each attribute is malloc'd, so the free function is free. + */ + attributes_byname = fr_hash_table_create(dict_attr_name_hash, + dict_attr_name_cmp, + fr_pool_free); + if (!attributes_byname) { + return -1; + } + + /* + * Create the table of attributes by value. There MAY + * be attributes of the same value. If there are, we + * pick the latest one. + */ + attributes_byvalue = fr_hash_table_create(dict_attr_value_hash, + dict_attr_value_cmp, + fr_pool_free); + if (!attributes_byvalue) { + return -1; + } + + values_byname = fr_hash_table_create(dict_value_name_hash, + dict_value_name_cmp, + fr_pool_free); + if (!values_byname) { + return -1; + } + + values_byvalue = fr_hash_table_create(dict_value_value_hash, + dict_value_value_cmp, + fr_pool_free); + if (!values_byvalue) { + return -1; + } + + value_fixup = NULL; /* just to be safe. */ + + if (my_dict_init(dir, fn, NULL, 0) < 0) + return -1; + + if (value_fixup) { + DICT_ATTR *a; + value_fixup_t *this, *next; + + for (this = value_fixup; this != NULL; this = next) { + next = this->next; + + a = dict_attrbyname(this->attrstr); + if (!a) { + fr_strerror_printf( + "dict_init: No ATTRIBUTE \"%s\" defined for VALUE \"%s\"", + this->attrstr, this->dval->name); + return -1; /* leak, but they should die... */ + } + + this->dval->attr = a->attr; + + /* + * Add the value into the dictionary. + */ + if (!fr_hash_table_replace(values_byname, + this->dval)) { + fr_strerror_printf("dict_addvalue: Duplicate value name %s for attribute %s", this->dval->name, a->name); + return -1; + } + + /* + * Allow them to use the old name, but + * prefer the new name when printing + * values. + */ + if (!fr_hash_table_finddata(values_byvalue, this->dval)) { + fr_hash_table_replace(values_byvalue, + this->dval); + } + free(this); + + /* + * Just so we don't lose track of things. + */ + value_fixup = next; + } + } + + /* + * Walk over all of the hash tables to ensure they're + * initialized. We do this because the threads may perform + * lookups, and we don't want multi-threaded re-ordering + * of the table entries. That would be bad. + */ + fr_hash_table_walk(vendors_byname, null_callback, NULL); + fr_hash_table_walk(vendors_byvalue, null_callback, NULL); + + fr_hash_table_walk(attributes_byname, null_callback, NULL); + fr_hash_table_walk(attributes_byvalue, null_callback, NULL); + + fr_hash_table_walk(values_byvalue, null_callback, NULL); + fr_hash_table_walk(values_byname, null_callback, NULL); + + return 0; +} + +/* + * Get an attribute by its numerical value. + */ +DICT_ATTR *dict_attrbyvalue(int attr) +{ + DICT_ATTR dattr; + + if ((attr > 0) && (attr < 256)) return dict_base_attrs[attr]; + + dattr.attr = attr; + /* BACON */ + dattr.vendor = VENDOR(attr) /*& 0x7fff*/; + /* END BACON */ + + return fr_hash_table_finddata(attributes_byvalue, &dattr); +} + +/* + * Get an attribute by its name. + */ +DICT_ATTR *dict_attrbyname(const char *name) +{ + DICT_ATTR *da; + uint32_t buffer[(sizeof(*da) + DICT_ATTR_MAX_NAME_LEN + 3)/4]; + + if (!name) return NULL; + + da = (DICT_ATTR *) buffer; + strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1); + + return fr_hash_table_finddata(attributes_byname, da); +} + +/* + * Associate a value with an attribute and return it. + */ +DICT_VALUE *dict_valbyattr(int attr, int value) +{ + DICT_VALUE dval, *dv; + + /* + * First, look up aliases. + */ + dval.attr = attr; + dval.name[0] = '\0'; + + /* + * Look up the attribute alias target, and use + * the correct attribute number if found. + */ + dv = fr_hash_table_finddata(values_byname, &dval); + if (dv) dval.attr = dv->value; + + dval.value = value; + + return fr_hash_table_finddata(values_byvalue, &dval); +} + +/* + * Get a value by its name, keyed off of an attribute. + */ +DICT_VALUE *dict_valbyname(int attr, const char *name) +{ + DICT_VALUE *my_dv, *dv; + uint32_t buffer[(sizeof(*my_dv) + DICT_VALUE_MAX_NAME_LEN + 3)/4]; + + if (!name) return NULL; + + my_dv = (DICT_VALUE *) buffer; + my_dv->attr = attr; + my_dv->name[0] = '\0'; + + /* + * Look up the attribute alias target, and use + * the correct attribute number if found. + */ + dv = fr_hash_table_finddata(values_byname, my_dv); + if (dv) my_dv->attr = dv->value; + + strlcpy(my_dv->name, name, DICT_VALUE_MAX_NAME_LEN + 1); + + return fr_hash_table_finddata(values_byname, my_dv); +} + +/* + * Get the vendor PEC based on the vendor name + * + * This is efficient only for small numbers of vendors. + */ +int dict_vendorbyname(const char *name) +{ + DICT_VENDOR *dv; + uint32_t buffer[(sizeof(*dv) + DICT_VENDOR_MAX_NAME_LEN + 3)/4]; + + if (!name) return 0; + + dv = (DICT_VENDOR *) buffer; + strlcpy(dv->name, name, DICT_VENDOR_MAX_NAME_LEN + 1); + + dv = fr_hash_table_finddata(vendors_byname, dv); + if (!dv) return 0; + + return dv->vendorpec; +} + +/* + * Return the vendor struct based on the PEC. + */ +DICT_VENDOR *dict_vendorbyvalue(int vendorpec) +{ + DICT_VENDOR dv; + + dv.vendorpec = vendorpec; + + return fr_hash_table_finddata(vendors_byvalue, &dv); +} +#endif