Kea 3.3.3
client_dictionary.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
10#include <util/str.h>
11
12#include <fstream>
13#include <limits>
14#include <sstream>
15
16#include <boost/lexical_cast.hpp>
17
18#include <radius_log.h>
19
20using namespace isc;
21using namespace isc::util;
22using namespace std;
23
24namespace isc {
25namespace radius {
26
27string
29 switch (static_cast<uint8_t>(value)) {
30 case PW_TYPE_STRING:
31 return ("string");
32 case PW_TYPE_INTEGER:
33 return ("integer");
34 case PW_TYPE_IPADDR:
35 return ("ipaddr");
37 return ("ipv6addr");
39 return ("ipv6prefix");
40 case PW_TYPE_VSA:
41 return ("vsa");
42 default:
43 // Impossible case.
44 return ("unknown?");
45 }
46}
47
49textToAttrValueType(const string& name) {
50 if (name == "string") {
51 return (PW_TYPE_STRING);
52 } else if ((name == "integer") || (name == "date")) {
53 return (PW_TYPE_INTEGER);
54 } else if (name == "ipaddr") {
55 return (PW_TYPE_IPADDR);
56 } else if (name == "ipv6addr") {
57 return (PW_TYPE_IPV6ADDR);
58 } else if (name == "ipv6prefix") {
59 return (PW_TYPE_IPV6PREFIX);
60 } else if (name == "vsa") {
61 return (PW_TYPE_VSA);
62 } else {
63 isc_throw(OutOfRange, "unknown AttrValueType name " << name);
64 }
65}
66
67AttrDefs&
69 static AttrDefs defs;
70 return (defs);
71}
72
74AttrDefs::getByType(const uint8_t type, const uint32_t vendor) const {
75 auto const& idx = container_.get<0>();
76 auto it = idx.find(boost::make_tuple(vendor, type));
77 if (it != idx.end()) {
78 return (*it);
79 }
80 return (AttrDefPtr());
81}
82
84AttrDefs::getByName(const string& name, const uint32_t vendor) const {
85 auto const& idx = container_.get<1>();
86 auto it = idx.find(boost::make_tuple(vendor, name));
87 if (it != idx.end()) {
88 return (*it);
89 }
90 auto alias = aliases_.find(boost::make_tuple(vendor, name));
91 if (alias != aliases_.end()) {
92 auto ita = idx.find(boost::make_tuple(vendor, alias->name_));
93 if (ita != idx.end()) {
94 return (*ita);
95 }
96 }
97 return (AttrDefPtr());
98}
99
100void
102 if (!def) {
103 return;
104 }
105 auto& idx1 = container_.get<1>();
106 auto it1 = idx1.find(boost::make_tuple(def->vendor_, def->name_));
107 if (it1 != idx1.end()) {
108 if ((def->type_ == (*it1)->type_) &&
109 (def->value_type_ == (*it1)->value_type_)) {
110 // Duplicate: ignore.
111 return;
112 }
113 ostringstream msg;
114 msg << "Illegal attribute redefinition of '" << def->name_ << "'";
115 if (def->vendor_ != 0) {
116 msg << " vendor " << def->vendor_;
117 }
118 msg << " type " << static_cast<unsigned>((*it1)->type_)
119 << " value type " << attrValueTypeToText((*it1)->value_type_)
120 << " by " << static_cast<unsigned>(def->type_)
121 << " " << attrValueTypeToText(def->value_type_);
122 isc_throw(BadValue, msg.str());
123 }
124 auto& idx0 = container_.get<0>();
125 auto it0 = idx0.find(boost::make_tuple(def->vendor_, def->type_));
126 if (it0 != idx0.end()) {
127 if (def->value_type_ == (*it0)->value_type_) {
128 // Alias.
129 AttrDefAlias alias(def->name_, (*it0)->name_, def->vendor_);
130 static_cast<void>(aliases_.insert(alias));
131 return;
132 }
133 ostringstream msg;
134 msg << "Illegal attribute redefinition of '" << (*it0)->name_ << "'";
135 if (def->vendor_ != 0) {
136 msg << " vendor " << def->vendor_;
137 }
138 msg << " type " << static_cast<unsigned>((*it0)->type_)
139 << " value type " << attrValueTypeToText((*it0)->value_type_)
140 << " by '" << def->name_ << "' "
141 << static_cast<unsigned>(def->type_) << " "
142 << attrValueTypeToText(def->value_type_);
143 isc_throw(BadValue, msg.str());
144 }
145 static_cast<void>(container_.insert(def));
146}
147
148string
149AttrDefs::getName(const uint8_t type, const uint32_t vendor) const {
150 AttrDefPtr def = getByType(type, vendor);
151 if (def) {
152 return (def->name_);
153 }
154 ostringstream oss;
155 oss << "Attribute-" << static_cast<unsigned>(type);
156 return (oss.str());
157}
158
160AttrDefs::getByName(const uint8_t type, const string& name,
161 const uint32_t vendor) const {
162 auto const& idx = ic_container_.get<0>();
163 auto it = idx.find(boost::make_tuple(vendor, type, name));
164 if (it != idx.end()) {
165 return (*it);
166 }
167 return (IntCstDefPtr());
168}
169
171AttrDefs::getByValue(const uint8_t type, const uint32_t value,
172 const uint32_t vendor) const {
173 auto const& idx = ic_container_.get<1>();
174 auto it = idx.find(boost::make_tuple(vendor, type, value));
175 if (it != idx.end()) {
176 return (*it);
177 }
178 return (IntCstDefPtr());
179}
180
181void
183 if (!def) {
184 return;
185 }
186 auto& idx = ic_container_.get<0>();
187 auto it = idx.find(boost::make_tuple(def->vendor_, def->type_, def->name_));
188 if (it != idx.end()) {
189 if (def->value_ == (*it)->value_) {
190 // Duplicate: ignore.
191 return;
192 }
193 if (def->type_ == PW_VENDOR_SPECIFIC) {
194 // Vendor id special case.
195 isc_throw(BadValue, "Illegal vendor id redefinition of '"
196 << def->name_ << "' value " << (*it)->value_
197 << " by " << def->value_);
198 }
199 ostringstream msg;
200 msg << "Illegal integer constant redefinition of '"
201 << def->name_ << "' for attribute '"
202 << getName(def->type_, def->vendor_) << "'";
203 if (def->vendor_ != 0) {
204 msg << " in vendor " << def->vendor_;
205 }
206 msg << " value " << (*it)->value_ << " by " << def->value_;
207 isc_throw(BadValue, msg.str());
208 }
209 static_cast<void>(ic_container_.insert(def));
210}
211
212void
213AttrDefs::parseLine(const string& line, uint32_t& vendor, unsigned int depth) {
214 // Ignore empty lines.
215 if (line.empty()) {
216 return;
217 }
218 // Ignore comments.
220 if (line[0] == '#') {
221 return;
222 }
223 // Take tokens.
224 auto tokens = str::tokens(line);
225 // Ignore blank lines.
226 if (tokens.empty()) {
227 return;
228 }
229 // $INCLUDE include.
230 if (tokens[0] == "$INCLUDE") {
231 if (tokens.size() != 2) {
232 isc_throw(Unexpected, "expected 2 tokens, got " << tokens.size());
233 }
234 readDictionary(tokens[1], vendor, depth + 1);
235 return;
236 }
237 // Attribute definition.
238 if (tokens[0] == "ATTRIBUTE") {
239 if (tokens.size() != 4) {
240 isc_throw(Unexpected, "expected 4 tokens, got " << tokens.size());
241 }
242 const string& name = tokens[1];
243 const string& type_str = tokens[2];
244 uint8_t type = 0;
245 try {
246 int64_t type64 = boost::lexical_cast<int64_t>(type_str);
247 // Ignore attribute types outside [0..255].
248 if ((type64 < numeric_limits<uint8_t>::min()) ||
249 (type64 > numeric_limits<uint8_t>::max())) {
250 return;
251 }
252 type = static_cast<uint8_t>(type64);
253 } catch (...) {
254 isc_throw(Unexpected, "can't parse attribute type " << type_str);
255 }
256 AttrValueType value_type = textToAttrValueType(tokens[3]);
257 if ((value_type == PW_TYPE_VSA) &&
258 ((vendor != 0) || (type != PW_VENDOR_SPECIFIC))) {
259 isc_throw(BadValue, "only Vendor-Specific (26) attribute can "
260 << "have the vsa data type");
261 }
262 AttrDefPtr def(new AttrDef(type, name, value_type, vendor));
263 add(def);
264 return;
265 }
266 // Integer constant definition.
267 if (tokens[0] == "VALUE") {
268 if (tokens.size() != 4) {
269 isc_throw(Unexpected, "expected 4 tokens, got " << tokens.size());
270 }
271 const string& attr_str = tokens[1];
272 AttrDefPtr attr = getByName(attr_str, vendor);
273 if (!attr) {
274 ostringstream msg;
275 msg << "unknown attribute '" << attr_str << "'";
276 if (vendor != 0) {
277 msg << " in vendor " << vendor;
278 }
279 isc_throw(Unexpected, msg.str());
280 }
281 if (attr->value_type_ != PW_TYPE_INTEGER) {
282 ostringstream msg;
283 msg << "attribute '" << attr_str << "'";
284 if (vendor != 0) {
285 msg << " in vendor " << vendor;
286 }
287 msg << " is not an integer attribute";
288 isc_throw(Unexpected, msg.str());
289 }
290 const string& name = tokens[2];
291 const string& value_str = tokens[3];
292 uint32_t value = 0;
293 try {
294 int64_t val = boost::lexical_cast<int64_t>(value_str);
295 if ((val < numeric_limits<int32_t>::min()) ||
296 (val > numeric_limits<uint32_t>::max())) {
297 isc_throw(Unexpected, "not 32 bit " << value_str);
298 }
299 value = static_cast<uint32_t>(val);
300 } catch (...) {
301 isc_throw(Unexpected, "can't parse integer value " << value_str);
302 }
303 IntCstDefPtr def(new IntCstDef(attr->type_, name, value, vendor));
304 add(def);
305 return;
306 }
307 // Vendor id definition.
308 if (tokens[0] == "VENDOR") {
309 if (tokens.size() != 3) {
310 isc_throw(Unexpected, "expected 3 tokens, got " << tokens.size());
311 }
312 const string& name = tokens[1];
313 const string& value_str = tokens[2];
314 uint32_t value = 0;
315 try {
316 int64_t val = boost::lexical_cast<int64_t>(value_str);
317 if ((val < numeric_limits<int32_t>::min()) ||
318 (val > numeric_limits<uint32_t>::max())) {
319 isc_throw(Unexpected, "not 32 bit " << value_str);
320 }
321 value = static_cast<uint32_t>(val);
322 } catch (...) {
323 isc_throw(Unexpected, "can't parse integer value " << value_str);
324 }
325 if (value == 0) {
326 isc_throw(Unexpected, "0 is reserved");
327 }
328 IntCstDefPtr def(new IntCstDef(PW_VENDOR_SPECIFIC, name, value));
329 add(def);
330 return;
331 }
332 // Begin vendor attribute definitions.
333 if (tokens[0] == "BEGIN-VENDOR") {
334 if (vendor != 0) {
335 isc_throw(Unexpected, "unsupported embedded begin vendor, "
336 << vendor << " is still open");
337 }
338 if (tokens.size() != 2) {
339 isc_throw(Unexpected, "expected 2 tokens, got " << tokens.size());
340 }
341 const string& vendor_str = tokens[1];
342 IntCstDefPtr vendor_cst = getByName(PW_VENDOR_SPECIFIC, vendor_str);
343 if (vendor_cst) {
344 vendor = vendor_cst->value_;
345 return;
346 }
347 try {
348 int64_t val = boost::lexical_cast<int64_t>(vendor_str);
349 if ((val < numeric_limits<int32_t>::min()) ||
350 (val > numeric_limits<uint32_t>::max())) {
351 isc_throw(Unexpected, "not 32 bit " << vendor_str);
352 }
353 vendor = static_cast<uint32_t>(val);
354 } catch (...) {
355 isc_throw(Unexpected, "can't parse integer value " << vendor_str);
356 }
357 if (vendor == 0) {
358 isc_throw(Unexpected, "0 is reserved");
359 }
360 return;
361 }
362 // End vendor attribute definitions.
363 if (tokens[0] == "END-VENDOR") {
364 if (vendor == 0) {
365 isc_throw(Unexpected, "no matching begin vendor");
366 }
367 if (tokens.size() != 2) {
368 isc_throw(Unexpected, "expected 2 tokens, got " << tokens.size());
369 }
370 const string& vendor_str = tokens[1];
371 IntCstDefPtr vendor_cst = getByName(PW_VENDOR_SPECIFIC, vendor_str);
372 if (vendor_cst) {
373 if (vendor_cst->value_ == vendor) {
374 vendor = 0;
375 return;
376 } else {
377 isc_throw(Unexpected, "begin vendor " << vendor
378 << " and end vendor " << vendor_cst->value_
379 << " do not match");
380 }
381 }
382 uint32_t value;
383 try {
384 int64_t val = boost::lexical_cast<int64_t>(vendor_str);
385 if ((val < numeric_limits<int32_t>::min()) ||
386 (val > numeric_limits<uint32_t>::max())) {
387 isc_throw(Unexpected, "not 32 bit " << vendor_str);
388 }
389 value = static_cast<uint32_t>(val);
390 } catch (...) {
391 isc_throw(Unexpected, "can't parse integer value " << vendor_str);
392 }
393 if (vendor == value) {
394 vendor = 0;
395 return;
396 }
397 isc_throw(Unexpected, "begin vendor " << vendor
398 << " and end vendor " << value << " do not match");
399 }
400 isc_throw(Unexpected, "unknown dictionary entry '" << tokens[0] << "'");
401}
402
403void
404AttrDefs::readDictionary(const string& path, uint32_t& vendor,
405 unsigned int depth) {
406 if (depth >= 5) {
407 isc_throw(BadValue, "Too many nested $INCLUDE");
408 }
409 ifstream ifs(path);
410 if (!ifs.is_open()) {
411 isc_throw(BadValue, "can't open dictionary '" << path << "': "
412 << strerror(errno));
413 }
414 if (!ifs.good()) {
415 isc_throw(BadValue, "bad dictionary '" << path << "'");
416 }
417 try {
418 readDictionary(ifs, vendor, depth);
419 ifs.close();
420 } catch (const exception& ex) {
421 ifs.close();
422 isc_throw(BadValue, ex.what() << " in dictionary '" << path << "'"
423 << (depth > 0 ? "," : ""));
424 }
425}
426
427void
428AttrDefs::readDictionary(istream& is, uint32_t& vendor, unsigned int depth) {
429 size_t lines = 0;
430 string line;
431 try {
432 while (is.good()) {
433 ++lines;
434 getline(is, line);
435 parseLine(line, vendor, depth);
436 }
437 if (!is.eof()) {
438 isc_throw(BadValue, "I/O error: " << strerror(errno));
439 }
440 } catch (const exception& ex) {
441 isc_throw(BadValue, ex.what() << " at line " << lines);
442 }
443}
444
445void
447 for (auto const& it : defs) {
448 // Check by name.
449 AttrDefPtr def = getByName(it.name_);
450 if (!def) {
451 isc_throw(Unexpected, "missing standard attribute definition for '"
452 << it.name_ << "'");
453 }
454 if (def->type_ != it.type_) {
455 isc_throw(Unexpected, "incorrect standard attribute definition "
456 << "for '" << it.name_ << "': type is "
457 << static_cast<unsigned>(def->type_) << ", must be "
458 << static_cast<unsigned>(it.type_));
459 }
460 if (def->value_type_ != it.value_type_) {
461 isc_throw(Unexpected, "incorrect standard attribute definition "
462 << "for '" << it.name_ << "': value type is "
463 << attrValueTypeToText(def->value_type_)
464 << ", must be "
465 << attrValueTypeToText(it.value_type_));
466 }
467 }
468}
469
470} // end of namespace isc::radius
471} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
RADIUS attribute aliases.
RADIUS attribute definition.
void add(AttrDefPtr def)
Add (or replace) an attribute definition.
static AttrDefs & instance()
Returns a single instance.
AttrDefContainer container_
Attribute definition container.
IntCstDefContainer ic_container_
Integer constant definition container.
AttrDefAliases aliases_
Attribute aliases.
void readDictionary(const std::string &path, uint32_t &vendor, unsigned int depth=0)
Read a dictionary from a file.
IntCstDefPtr getByValue(const uint8_t type, const uint32_t value, const uint32_t vendor=0) const
Get integer constant definition by attribute type and value.
void parseLine(const std::string &line, uint32_t &vendor, unsigned int depth)
Parse a dictionary line.
std::string getName(const uint8_t type, const uint32_t vendor=0) const
Get attribute name.
AttrDefPtr getByName(const std::string &name, const uint32_t vendor=0) const
Get attribute definition by name and vendor.
void checkStandardDefs(const AttrDefList &defs) const
Check if a list of standard attribute definitions are available and correct.
AttrDefPtr getByType(const uint8_t type, const uint32_t vendor=0) const
Get attribute definition by type and vendor.
RADIUS integer constant definitions.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< IntCstDef > IntCstDefPtr
Shared pointers to Integer constant definition.
std::list< AttrDef > AttrDefList
List of Attribute definitions.
boost::shared_ptr< AttrDef > AttrDefPtr
Shared pointers to Attribute definition.
AttrValueType textToAttrValueType(const string &name)
AttrValueType name -> value function.
string attrValueTypeToText(const AttrValueType value)
AttrValueType value -> name function.
AttrValueType
Attribute value types.
vector< string > tokens(const string &text, const string &delim, bool escape)
Split string into tokens.
Definition str.cc:52
Defines the logger used by the top-level component of kea-lfc.