Kea  2.3.3-git
client_class_def_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2022 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  // Parse valid lifetime triplet.
233  Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
234 
235  Triplet<uint32_t> preferred_lft;
236  if (family != AF_INET) {
237  // Parse preferred lifetime triplet.
238  preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
239  }
240 
241  // Sanity checks on built-in classes
242  for (auto bn : builtinNames) {
243  if (name == bn) {
244  if (required) {
245  isc_throw(DhcpConfigError, "built-in class '" << name
246  << "' only-if-required flag must be false");
247  }
248  if (!test.empty()) {
249  isc_throw(DhcpConfigError, "built-in class '" << name
250  << "' test expression must be empty");
251  }
252  }
253  }
254 
255  // Sanity checks on DROP
256  if (name == "DROP") {
257  if (required) {
258  isc_throw(DhcpConfigError, "special class '" << name
259  << "' only-if-required flag must be false");
260  }
261  // depend_on_known is now allowed
262  }
263 
264  // Add the client class definition
265  try {
266  class_dictionary->addClass(name, match_expr, test, required,
267  depend_on_known, options, defs,
268  user_context, next_server, sname, filename,
269  valid_lft, preferred_lft, is_template);
270  } catch (const std::exception& ex) {
271  std::ostringstream s;
272  s << "Can't add class: " << ex.what();
273  // Append position of the error in JSON string if required.
274  if (append_error_position) {
275  s << " (" << class_def_cfg->getPosition() << ")";
276  }
277  isc_throw(DhcpConfigError, s.str());
278  }
279 }
280 
281 void
282 ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_cfg,
283  const uint16_t family) {
284  // Make sure that the client class definition is stored in a map.
285  if (!class_def_cfg || (class_def_cfg->getType() != Element::map)) {
286  isc_throw(DhcpConfigError, "client class definition is not a map");
287  }
288 
289  // Common v4 and v6 parameters supported for the client class.
290  static std::set<std::string> supported_params = { "name",
291  "test",
292  "option-data",
293  "user-context",
294  "only-if-required",
295  "valid-lifetime",
296  "min-valid-lifetime",
297  "max-valid-lifetime",
298  "template-test"};
299 
300  // The v4 client class supports additional parameters.
301  static std::set<std::string> supported_params_v4 = { "option-def",
302  "next-server",
303  "server-hostname",
304  "boot-file-name" };
305 
306  // The v6 client class supports additional parameters.
307  static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
308  "min-preferred-lifetime",
309  "max-preferred-lifetime" };
310 
311  // Iterate over the specified parameters and check if they are all supported.
312  for (auto name_value_pair : class_def_cfg->mapValue()) {
313  if ((supported_params.count(name_value_pair.first) > 0) ||
314  ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
315  ((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
316  continue;
317  } else {
318  isc_throw(DhcpConfigError, "unsupported client class parameter '"
319  << name_value_pair.first << "'");
320  }
321  }
322 }
323 
324 boost::shared_ptr<OptionDataListParser>
325 ClientClassDefParser::createOptionDataListParser(const uint16_t address_family,
326  CfgOptionDefPtr cfg_option_def) const {
327  auto parser = boost::make_shared<OptionDataListParser>(address_family, cfg_option_def);
328  return (parser);
329 }
330 
331 // ****************** ClientClassDefListParser ************************
332 
334 ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
335  uint16_t family, bool check_dependencies) {
337  BOOST_FOREACH(ConstElementPtr client_class_def,
338  client_class_def_list->listValue()) {
339  ClientClassDefParser parser;
340  parser.parse(dictionary, client_class_def, family, true, check_dependencies);
341  }
342  return (dictionary);
343 }
344 
345 } // end of namespace isc::dhcp
346 } // end of namespace isc
Parser for a single client class definition.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:745
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.
isc::dhcp::Expression expression
Parsed expression (output tokens are stored here)
Definition: eval_context.h:67
STL namespace.
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
OptionDefinitionPtr parse(isc::data::ConstElementPtr option_def)
Parses an entry that describes single option definition.
Definition: dhcp_parsers.cc:98
Maintains a list of ClientClassDef&#39;s.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
bool isClientClassDefined(ClientClassDictionaryPtr &class_dictionary, bool &depend_on_known, const ClientClass &client_class)
Check if a client class name is already defined, i.e.
Parser for a logical expression.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: edns.h:19
Represents option data configuration for the DHCP server.
Definition: cfg_option.h:318
Parsers for client class definitions.
To be removed. Please use ConfigError instead.
Parser for a single option definition.
Definition: dhcp_parsers.h:228
boost::shared_ptr< ClientClassDictionary > ClientClassDictionaryPtr
Defines a pointer to a ClientClassDictionary.
Evaluation context, an interface to the expression evaluation.
Definition: eval_context.h:34
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
Represents option definitions used by the DHCP server.
std::list< std::string > builtinNames
List of classes for which test expressions cannot be defined.
static size_t setListDefaults(isc::data::ConstElementPtr list, const SimpleDefaults &default_values)
Sets the default values for all entries in a list.
Defines the logger used by the top-level component of kea-lfc.
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition: token.h:28
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.
ParserType
Specifies what type of expression the parser is expected to see.
Definition: eval_context.h:38
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
std::string ClientClass
Defines a single class name.
Definition: classify.h:42
std::function< bool(const ClientClass &)> CheckDefined
Type of the check defined function.
Definition: eval_context.h:44
Defines classes for storing client class definitions.
boost::shared_ptr< Expression > ExpressionPtr
Definition: token.h:30
bool parseString(const std::string &str, ParserType type=PARSER_BOOL)
Run the parser on the string specified.
Definition: eval_context.cc:37