Kea  2.3.7
client_class_def_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2023 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 #include <dhcp/libdhcp++.h>
9 #include <dhcpsrv/cfgmgr.h>
15 #include <eval/eval_context.h>
16 #include <asiolink/io_address.h>
17 #include <asiolink/io_error.h>
18 
19 #include <boost/foreach.hpp>
20 #include <algorithm>
21 #include <sstream>
22 
23 using namespace isc::data;
24 using namespace isc::asiolink;
25 using namespace isc::util;
26 using namespace std;
27 
31 
32 namespace isc {
33 namespace dhcp {
34 
35 // ********************** ExpressionParser ****************************
36 
37 void
38 ExpressionParser::parse(ExpressionPtr& expression,
39  ConstElementPtr expression_cfg,
40  uint16_t family,
41  EvalContext::CheckDefined check_defined,
42  EvalContext::ParserType parser_type) {
43  if (expression_cfg->getType() != Element::string) {
44  isc_throw(DhcpConfigError, "expression ["
45  << expression_cfg->str() << "] must be a string, at ("
46  << expression_cfg->getPosition() << ")");
47  }
48 
49  // Get the expression's text via getValue() as the text returned
50  // by str() enclosed in quotes.
51  std::string value;
52  expression_cfg->getValue(value);
53 
54  if (parser_type == EvalContext::PARSER_STRING && value.empty()) {
55  isc_throw(DhcpConfigError, "expression can not be empty at ("
56  << expression_cfg->getPosition() << ")");
57  }
58 
59  try {
60  EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
61  check_defined);
62  eval_ctx.parseString(value, parser_type);
63  expression.reset(new Expression());
64  *expression = eval_ctx.expression;
65  } catch (const std::exception& ex) {
66  // Append position if there is a failure.
68  "expression: [" << value
69  << "] error: " << ex.what() << " at ("
70  << expression_cfg->getPosition() << ")");
71  }
72 }
73 
74 // ********************** ClientClassDefParser ****************************
75 
76 void
77 ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
78  ConstElementPtr class_def_cfg,
79  uint16_t family,
80  bool append_error_position,
81  bool check_dependencies) {
82  // name is now mandatory, so let's deal with it first.
83  std::string name = getString(class_def_cfg, "name");
84  if (name.empty()) {
86  "not empty parameter 'name' is required "
87  << getPosition("name", class_def_cfg) << ")");
88  }
89 
90  EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL;
91 
92  // Let's try to parse the template-test expression
93  bool is_template = false;
94 
95  // Parse matching expression
96  ExpressionPtr match_expr;
97  ConstElementPtr test_cfg = class_def_cfg->get("test");
98  ConstElementPtr template_test_cfg = class_def_cfg->get("template-test");
99  if (test_cfg && template_test_cfg) {
100  isc_throw(DhcpConfigError, "can not use both 'test' and 'template-test' ("
101  << test_cfg->getPosition() << ") and ("
102  << template_test_cfg->getPosition() << ")");
103  }
104  std::string test;
105  bool depend_on_known = false;
106  EvalContext::CheckDefined check_defined = EvalContext::acceptAll;
107  if (template_test_cfg) {
108  test_cfg = template_test_cfg;
109  parser_type = EvalContext::PARSER_STRING;
110  is_template = true;
111  } else {
112  check_defined = [&class_dictionary, &depend_on_known, check_dependencies](const ClientClass& cclass) {
113  return (!check_dependencies || isClientClassDefined(class_dictionary, depend_on_known, cclass));
114  };
115  }
116 
117  if (test_cfg) {
118  ExpressionParser parser;
119  parser.parse(match_expr, test_cfg, family, check_defined, parser_type);
120  test = test_cfg->stringValue();
121  }
122 
123  // Parse option def
124  CfgOptionDefPtr defs(new CfgOptionDef());
125  ConstElementPtr option_defs = class_def_cfg->get("option-def");
126  if (option_defs) {
127  // Apply defaults
128  SimpleParser::setListDefaults(option_defs,
129  family == AF_INET ?
130  SimpleParser4::OPTION4_DEF_DEFAULTS :
131  SimpleParser6::OPTION6_DEF_DEFAULTS);
132 
133  OptionDefParser parser(family);
134  BOOST_FOREACH(ConstElementPtr option_def, option_defs->listValue()) {
135  OptionDefinitionPtr def = parser.parse(option_def);
136 
137  // Verify if the definition is for an option which is in a deferred
138  // processing list.
139  if (!LibDHCP::shouldDeferOptionUnpack(def->getOptionSpaceName(),
140  def->getCode())) {
142  "Not allowed option definition for code '"
143  << def->getCode() << "' in space '"
144  << def->getOptionSpaceName() << "' at ("
145  << option_def->getPosition() << ")");
146  }
147  try {
148  defs->add(def);
149  } catch (const std::exception& ex) {
150  // Sanity check: it should never happen
151  isc_throw(DhcpConfigError, ex.what() << " ("
152  << option_def->getPosition() << ")");
153  }
154  }
155  }
156 
157  // Parse option data
158  CfgOptionPtr options(new CfgOption());
159  ConstElementPtr option_data = class_def_cfg->get("option-data");
160  if (option_data) {
161  auto opts_parser = createOptionDataListParser(family, defs);
162  opts_parser->parse(options, option_data);
163  }
164 
165  // Parse user context
166  ConstElementPtr user_context = class_def_cfg->get("user-context");
167  if (user_context) {
168  if (user_context->getType() != Element::map) {
169  isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
170  << user_context->getPosition() << ")");
171  }
172  }
173 
174  // Let's try to parse the only-if-required flag
175  bool required = false;
176  if (class_def_cfg->contains("only-if-required")) {
177  required = getBoolean(class_def_cfg, "only-if-required");
178  }
179 
180  // Let's try to parse the next-server field
181  IOAddress next_server("0.0.0.0");
182  if (class_def_cfg->contains("next-server")) {
183  std::string next_server_txt = getString(class_def_cfg, "next-server");
184  try {
185  next_server = IOAddress(next_server_txt);
186  } catch (const IOError& ex) {
188  "Invalid next-server value specified: '"
189  << next_server_txt << "' ("
190  << getPosition("next-server", class_def_cfg) << ")");
191  }
192 
193  if (next_server.getFamily() != AF_INET) {
194  isc_throw(DhcpConfigError, "Invalid next-server value: '"
195  << next_server_txt << "', must be IPv4 address ("
196  << getPosition("next-server", class_def_cfg) << ")");
197  }
198 
199  if (next_server.isV4Bcast()) {
200  isc_throw(DhcpConfigError, "Invalid next-server value: '"
201  << next_server_txt << "', must not be a broadcast ("
202  << getPosition("next-server", class_def_cfg) << ")");
203  }
204  }
205 
206  // Let's try to parse server-hostname
207  std::string sname;
208  if (class_def_cfg->contains("server-hostname")) {
209  sname = getString(class_def_cfg, "server-hostname");
210 
211  if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
212  isc_throw(DhcpConfigError, "server-hostname must be at most "
213  << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
214  << sname.length() << " ("
215  << getPosition("server-hostname", class_def_cfg) << ")");
216  }
217  }
218 
219  // Let's try to parse boot-file-name
220  std::string filename;
221  if (class_def_cfg->contains("boot-file-name")) {
222  filename = getString(class_def_cfg, "boot-file-name");
223 
224  if (filename.length() > Pkt4::MAX_FILE_LEN) {
225  isc_throw(DhcpConfigError, "boot-file-name must be at most "
226  << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
227  << filename.length() << " ("
228  << getPosition("boot-file-name", class_def_cfg) << ")");
229  }
230  }
231 
232  Optional<uint32_t> offer_lft;
233  if (class_def_cfg->contains("offer-lifetime")) {
234  auto value = getInteger(class_def_cfg, "offer-lifetime");
235  if (value < 0) {
236  isc_throw(DhcpConfigError, "the value of offer-lifetime '"
237  << value << "' must be a positive number ("
238  << getPosition("offer-lifetime", class_def_cfg) << ")");
239  }
240 
241  offer_lft = value;
242  }
243 
244  // Parse valid lifetime triplet.
245  Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
246 
247  Triplet<uint32_t> preferred_lft;
248  if (family != AF_INET) {
249  // Parse preferred lifetime triplet.
250  preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
251  }
252 
253  // Sanity checks on built-in classes
254  for (auto bn : builtinNames) {
255  if (name == bn) {
256  if (required) {
257  isc_throw(DhcpConfigError, "built-in class '" << name
258  << "' only-if-required flag must be false");
259  }
260  if (!test.empty()) {
261  isc_throw(DhcpConfigError, "built-in class '" << name
262  << "' test expression must be empty");
263  }
264  }
265  }
266 
267  // Sanity checks on DROP
268  if (name == "DROP") {
269  if (required) {
270  isc_throw(DhcpConfigError, "special class '" << name
271  << "' only-if-required flag must be false");
272  }
273  // depend_on_known is now allowed
274  }
275 
276  // Add the client class definition
277  try {
278  class_dictionary->addClass(name, match_expr, test, required,
279  depend_on_known, options, defs,
280  user_context, next_server, sname, filename,
281  valid_lft, preferred_lft, is_template, offer_lft);
282  } catch (const std::exception& ex) {
283  std::ostringstream s;
284  s << "Can't add class: " << ex.what();
285  // Append position of the error in JSON string if required.
286  if (append_error_position) {
287  s << " (" << class_def_cfg->getPosition() << ")";
288  }
289  isc_throw(DhcpConfigError, s.str());
290  }
291 }
292 
293 void
294 ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_cfg,
295  const uint16_t family) {
296  // Make sure that the client class definition is stored in a map.
297  if (!class_def_cfg || (class_def_cfg->getType() != Element::map)) {
298  isc_throw(DhcpConfigError, "client class definition is not a map");
299  }
300 
301  // Common v4 and v6 parameters supported for the client class.
302  static std::set<std::string> supported_params = { "name",
303  "test",
304  "option-data",
305  "user-context",
306  "only-if-required",
307  "valid-lifetime",
308  "min-valid-lifetime",
309  "max-valid-lifetime",
310  "template-test"};
311 
312  // The v4 client class supports additional parameters.
313  static std::set<std::string> supported_params_v4 = { "option-def",
314  "next-server",
315  "server-hostname",
316  "boot-file-name" };
317 
318  // The v6 client class supports additional parameters.
319  static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
320  "min-preferred-lifetime",
321  "max-preferred-lifetime" };
322 
323  // Iterate over the specified parameters and check if they are all supported.
324  for (auto name_value_pair : class_def_cfg->mapValue()) {
325  if ((supported_params.count(name_value_pair.first) > 0) ||
326  ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
327  ((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
328  continue;
329  } else {
330  isc_throw(DhcpConfigError, "unsupported client class parameter '"
331  << name_value_pair.first << "'");
332  }
333  }
334 }
335 
336 boost::shared_ptr<OptionDataListParser>
337 ClientClassDefParser::createOptionDataListParser(const uint16_t address_family,
338  CfgOptionDefPtr cfg_option_def) const {
339  auto parser = boost::make_shared<OptionDataListParser>(address_family, cfg_option_def);
340  return (parser);
341 }
342 
343 // ****************** ClientClassDefListParser ************************
344 
346 ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
347  uint16_t family, bool check_dependencies) {
349  BOOST_FOREACH(ConstElementPtr client_class_def,
350  client_class_def_list->listValue()) {
351  ClientClassDefParser parser;
352  parser.parse(dictionary, client_class_def, family, true, check_dependencies);
353  }
354  return (dictionary);
355 }
356 
357 } // end of namespace isc::dhcp
358 } // end of namespace isc
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static size_t setListDefaults(isc::data::ConstElementPtr list, const SimpleDefaults &default_values)
Sets the default values for all entries in a list.
Represents option definitions used by the DHCP server.
Represents option data configuration for the DHCP server.
Definition: cfg_option.h:351
Parser for a single client class definition.
void parse(ClientClassDictionaryPtr &class_dictionary, isc::data::ConstElementPtr client_class_def, uint16_t family, bool append_error_position=true, bool check_dependencies=true)
Parses an entry that describes single client class definition.
Maintains a list of ClientClassDef's.
To be removed. Please use ConfigError instead.
Parser for a logical expression.
void parse(ExpressionPtr &expression, isc::data::ConstElementPtr expression_cfg, uint16_t family, isc::eval::EvalContext::CheckDefined check_defined=isc::eval::EvalContext::acceptAll, isc::eval::EvalContext::ParserType parser_type=isc::eval::EvalContext::PARSER_BOOL)
Parses an expression configuration element into an Expression.
Parser for a single option definition.
Definition: dhcp_parsers.h:228
OptionDefinitionPtr parse(isc::data::ConstElementPtr option_def)
Parses an entry that describes single option definition.
Evaluation context, an interface to the expression evaluation.
Definition: eval_context.h:34
std::function< bool(const ClientClass &)> CheckDefined
Type of the check defined function.
Definition: eval_context.h:44
bool parseString(const std::string &str, ParserType type=PARSER_BOOL)
Run the parser on the string specified.
Definition: eval_context.cc:37
ParserType
Specifies what type of expression the parser is expected to see.
Definition: eval_context.h:38
isc::dhcp::Expression expression
Parsed expression (output tokens are stored here)
Definition: eval_context.h:67
Defines classes for storing client class definitions.
Parsers for client class definitions.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
std::string ClientClass
Defines a single class name.
Definition: classify.h:42
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:780
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
boost::shared_ptr< Expression > ExpressionPtr
Definition: token.h:30
boost::shared_ptr< ClientClassDictionary > ClientClassDictionaryPtr
Defines a pointer to a ClientClassDictionary.
bool isClientClassDefined(ClientClassDictionaryPtr &class_dictionary, bool &depend_on_known, const ClientClass &client_class)
Check if a client class name is already defined, i.e.
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition: token.h:28
std::list< std::string > builtinNames
List of classes for which test expressions cannot be defined.
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.