Kea  2.1.7-git
client_class_def_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2021 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  if (expression_cfg->getType() != Element::string) {
43  isc_throw(DhcpConfigError, "expression ["
44  << expression_cfg->str() << "] must be a string, at ("
45  << expression_cfg->getPosition() << ")");
46  }
47 
48  // Get the expression's text via getValue() as the text returned
49  // by str() enclosed in quotes.
50  std::string value;
51  expression_cfg->getValue(value);
52  try {
53  EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
54  check_defined);
55  eval_ctx.parseString(value);
56  expression.reset(new Expression());
57  *expression = eval_ctx.expression;
58  } catch (const std::exception& ex) {
59  // Append position if there is a failure.
61  "expression: [" << value
62  << "] error: " << ex.what() << " at ("
63  << expression_cfg->getPosition() << ")");
64  }
65 }
66 
67 // ********************** ClientClassDefParser ****************************
68 
69 void
70 ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
71  ConstElementPtr class_def_cfg,
72  uint16_t family,
73  bool append_error_position,
74  bool check_dependencies) {
75  // name is now mandatory, so let's deal with it first.
76  std::string name = getString(class_def_cfg, "name");
77  if (name.empty()) {
79  "not empty parameter 'name' is required "
80  << getPosition("name", class_def_cfg) << ")");
81  }
82 
83  // Parse matching expression
84  ExpressionPtr match_expr;
85  ConstElementPtr test_cfg = class_def_cfg->get("test");
86  std::string test;
87  bool depend_on_known = false;
88  if (test_cfg) {
89  ExpressionParser parser;
90  auto check_defined =
91  [&class_dictionary, &depend_on_known, check_dependencies]
92  (const ClientClass& cclass) {
93  return (!check_dependencies || isClientClassDefined(class_dictionary,
94  depend_on_known,
95  cclass));
96  };
97  parser.parse(match_expr, test_cfg, family, check_defined);
98  test = test_cfg->stringValue();
99  }
100 
101  // Parse option def
102  CfgOptionDefPtr defs(new CfgOptionDef());
103  ConstElementPtr option_defs = class_def_cfg->get("option-def");
104  if (option_defs) {
105  // Apply defaults
106  SimpleParser::setListDefaults(option_defs,
107  family == AF_INET ?
108  SimpleParser4::OPTION4_DEF_DEFAULTS :
109  SimpleParser6::OPTION6_DEF_DEFAULTS);
110 
111  OptionDefParser parser(family);
112  BOOST_FOREACH(ConstElementPtr option_def, option_defs->listValue()) {
113  OptionDefinitionPtr def = parser.parse(option_def);
114 
115  // Verify if the definition is for an option which is in a deferred
116  // processing list.
117  if (!LibDHCP::shouldDeferOptionUnpack(def->getOptionSpaceName(),
118  def->getCode())) {
120  "Not allowed option definition for code '"
121  << def->getCode() << "' in space '"
122  << def->getOptionSpaceName() << "' at ("
123  << option_def->getPosition() << ")");
124  }
125  try {
126  defs->add(def);
127  } catch (const std::exception& ex) {
128  // Sanity check: it should never happen
129  isc_throw(DhcpConfigError, ex.what() << " ("
130  << option_def->getPosition() << ")");
131  }
132  }
133  }
134 
135  // Parse option data
136  CfgOptionPtr options(new CfgOption());
137  ConstElementPtr option_data = class_def_cfg->get("option-data");
138  if (option_data) {
139  auto opts_parser = createOptionDataListParser(family, defs);
140  opts_parser->parse(options, option_data);
141  }
142 
143  // Parse user context
144  ConstElementPtr user_context = class_def_cfg->get("user-context");
145  if (user_context) {
146  if (user_context->getType() != Element::map) {
147  isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
148  << user_context->getPosition() << ")");
149  }
150  }
151 
152  // Let's try to parse the only-if-required flag
153  bool required = false;
154  if (class_def_cfg->contains("only-if-required")) {
155  required = getBoolean(class_def_cfg, "only-if-required");
156  }
157 
158  // Let's try to parse the next-server field
159  IOAddress next_server("0.0.0.0");
160  if (class_def_cfg->contains("next-server")) {
161  std::string next_server_txt = getString(class_def_cfg, "next-server");
162  try {
163  next_server = IOAddress(next_server_txt);
164  } catch (const IOError& ex) {
166  "Invalid next-server value specified: '"
167  << next_server_txt << "' ("
168  << getPosition("next-server", class_def_cfg) << ")");
169  }
170 
171  if (next_server.getFamily() != AF_INET) {
172  isc_throw(DhcpConfigError, "Invalid next-server value: '"
173  << next_server_txt << "', must be IPv4 address ("
174  << getPosition("next-server", class_def_cfg) << ")");
175  }
176 
177  if (next_server.isV4Bcast()) {
178  isc_throw(DhcpConfigError, "Invalid next-server value: '"
179  << next_server_txt << "', must not be a broadcast ("
180  << getPosition("next-server", class_def_cfg) << ")");
181  }
182  }
183 
184  // Let's try to parse server-hostname
185  std::string sname;
186  if (class_def_cfg->contains("server-hostname")) {
187  sname = getString(class_def_cfg, "server-hostname");
188 
189  if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
190  isc_throw(DhcpConfigError, "server-hostname must be at most "
191  << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
192  << sname.length() << " ("
193  << getPosition("server-hostname", class_def_cfg) << ")");
194  }
195  }
196 
197  // Let's try to parse boot-file-name
198  std::string filename;
199  if (class_def_cfg->contains("boot-file-name")) {
200  filename = getString(class_def_cfg, "boot-file-name");
201 
202  if (filename.length() > Pkt4::MAX_FILE_LEN) {
203  isc_throw(DhcpConfigError, "boot-file-name must be at most "
204  << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
205  << filename.length() << " ("
206  << getPosition("boot-file-name", class_def_cfg) << ")");
207  }
208 
209  }
210 
211  // Parse valid lifetime triplet.
212  Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
213 
214  Triplet<uint32_t> preferred_lft;
215  if (family != AF_INET) {
216  // Parse preferred lifetime triplet.
217  preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
218  }
219 
220  // Sanity checks on built-in classes
221  for (auto bn : builtinNames) {
222  if (name == bn) {
223  if (required) {
224  isc_throw(DhcpConfigError, "built-in class '" << name
225  << "' only-if-required flag must be false");
226  }
227  if (!test.empty()) {
228  isc_throw(DhcpConfigError, "built-in class '" << name
229  << "' test expression must be empty");
230  }
231  }
232  }
233 
234  // Sanity checks on DROP
235  if (name == "DROP") {
236  if (required) {
237  isc_throw(DhcpConfigError, "special class '" << name
238  << "' only-if-required flag must be false");
239  }
240  // depend_on_known is now allowed
241  }
242 
243  // Add the client class definition
244  try {
245  class_dictionary->addClass(name, match_expr, test, required,
246  depend_on_known, options, defs,
247  user_context, next_server, sname, filename,
248  valid_lft, preferred_lft);
249  } catch (const std::exception& ex) {
250  std::ostringstream s;
251  s << "Can't add class: " << ex.what();
252  // Append position of the error in JSON string if required.
253  if (append_error_position) {
254  s << " (" << class_def_cfg->getPosition() << ")";
255  }
256  isc_throw(DhcpConfigError, s.str());
257  }
258 }
259 
260 void
261 ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_cfg,
262  const uint16_t family) {
263  // Make sure that the client class definition is stored in a map.
264  if (!class_def_cfg || (class_def_cfg->getType() != Element::map)) {
265  isc_throw(DhcpConfigError, "client class definition is not a map");
266  }
267 
268  // Common v4 and v6 parameters supported for the client class.
269  static std::set<std::string> supported_params = { "name",
270  "test",
271  "option-data",
272  "user-context",
273  "only-if-required",
274  "valid-lifetime",
275  "min-valid-lifetime",
276  "max-valid-lifetime" };
277 
278 
279  // The v4 client class supports additional parameters.
280  static std::set<std::string> supported_params_v4 = { "option-def",
281  "next-server",
282  "server-hostname",
283  "boot-file-name" };
284 
285  // The v6 client class supports additional parameters.
286  static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
287  "min-preferred-lifetime",
288  "max-preferred-lifetime" };
289 
290  // Iterate over the specified parameters and check if they are all supported.
291  for (auto name_value_pair : class_def_cfg->mapValue()) {
292  if ((supported_params.count(name_value_pair.first) > 0) ||
293  ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
294  ((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
295  continue;
296  } else {
297  isc_throw(DhcpConfigError, "unsupported client class parameter '"
298  << name_value_pair.first << "'");
299  }
300  }
301 }
302 
303 boost::shared_ptr<OptionDataListParser>
304 ClientClassDefParser::createOptionDataListParser(const uint16_t address_family,
305  CfgOptionDefPtr cfg_option_def) const {
306  auto parser = boost::make_shared<OptionDataListParser>(address_family, cfg_option_def);
307  return (parser);
308 }
309 
310 // ****************** ClientClassDefListParser ************************
311 
313 ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
314  uint16_t family, bool check_dependencies) {
316  BOOST_FOREACH(ConstElementPtr client_class_def,
317  client_class_def_list->listValue()) {
318  ClientClassDefParser parser;
319  parser.parse(dictionary, client_class_def, family, true, check_dependencies);
320  }
321  return (dictionary);
322 }
323 
324 } // end of namespace isc::dhcp
325 } // end of namespace isc
Parser for a single client class definition.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:706
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:97
void parse(ExpressionPtr &expression, isc::data::ConstElementPtr expression_cfg, uint16_t family, isc::eval::EvalContext::CheckDefined check_defined=isc::eval::EvalContext::acceptAll)
Parses an expression configuration element into an Expression.
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:314
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.
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
std::string ClientClass
Defines a single class name.
Definition: classify.h:40
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