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