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 
9 #include <eval/dependency.h>
10 #include <dhcpsrv/client_class_def.h>
11 #include <dhcpsrv/cfgmgr.h>
12 #include <dhcpsrv/parsers/client_class_def_parser.h>
13 #include <boost/foreach.hpp>
14 
15 #include <queue>
16 
17 using namespace isc::data;
18 
19 namespace isc {
20 namespace dhcp {
21 
22 //********** ClientClassDef ******************//
23 
ClientClassDef(const std::string & name,const ExpressionPtr & match_expr,const CfgOptionPtr & cfg_option)24 ClientClassDef::ClientClassDef(const std::string& name,
25                                const ExpressionPtr& match_expr,
26                                const CfgOptionPtr& cfg_option)
27     : UserContext(), CfgToElement(), StampedElement(), name_(name),
28       match_expr_(match_expr), required_(false), depend_on_known_(false),
29       cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
30       valid_(), preferred_() {
31 
32     // Name can't be blank
33     if (name_.empty()) {
34         isc_throw(BadValue, "Client Class name cannot be blank");
35     }
36 
37     // We permit an empty expression for now.  This will likely be useful
38     // for automatic classes such as vendor class.
39     // For classes without options, make sure we have an empty collection
40     if (!cfg_option_) {
41         cfg_option_.reset(new CfgOption());
42     }
43 }
44 
ClientClassDef(const ClientClassDef & rhs)45 ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
46     : UserContext(rhs), CfgToElement(rhs), StampedElement(rhs), name_(rhs.name_),
47       match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_),
48       depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()),
49       next_server_(rhs.next_server_), sname_(rhs.sname_),
50       filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_) {
51 
52     if (rhs.match_expr_) {
53         match_expr_.reset(new Expression());
54         *match_expr_ = *(rhs.match_expr_);
55     }
56 
57     if (rhs.cfg_option_def_) {
58         cfg_option_def_.reset(new CfgOptionDef());
59         rhs.cfg_option_def_->copyTo(*cfg_option_def_);
60     }
61 
62     if (rhs.cfg_option_) {
63         rhs.cfg_option_->copyTo(*cfg_option_);
64     }
65 }
66 
~ClientClassDef()67 ClientClassDef::~ClientClassDef() {
68 }
69 
70 std::string
getName() const71 ClientClassDef::getName() const {
72     return (name_);
73 }
74 
75 void
setName(const std::string & name)76 ClientClassDef::setName(const std::string& name) {
77     name_ = name;
78 }
79 
80 const ExpressionPtr&
getMatchExpr() const81 ClientClassDef::getMatchExpr() const {
82     return (match_expr_);
83 }
84 
85 void
setMatchExpr(const ExpressionPtr & match_expr)86 ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
87     match_expr_ = match_expr;
88 }
89 
90 std::string
getTest() const91 ClientClassDef::getTest() const {
92     return (test_);
93 }
94 
95 void
setTest(const std::string & test)96 ClientClassDef::setTest(const std::string& test) {
97     test_ = test;
98 }
99 
100 bool
getRequired() const101 ClientClassDef::getRequired() const {
102     return (required_);
103 }
104 
105 void
setRequired(bool required)106 ClientClassDef::setRequired(bool required) {
107     required_ = required;
108 }
109 
110 bool
getDependOnKnown() const111 ClientClassDef::getDependOnKnown() const {
112     return (depend_on_known_);
113 }
114 
115 void
setDependOnKnown(bool depend_on_known)116 ClientClassDef::setDependOnKnown(bool depend_on_known) {
117     depend_on_known_ = depend_on_known;
118 }
119 
120 const CfgOptionDefPtr&
getCfgOptionDef() const121 ClientClassDef::getCfgOptionDef() const {
122     return (cfg_option_def_);
123 }
124 
125 void
setCfgOptionDef(const CfgOptionDefPtr & cfg_option_def)126 ClientClassDef::setCfgOptionDef(const CfgOptionDefPtr& cfg_option_def) {
127     cfg_option_def_ = cfg_option_def;
128 }
129 
130 const CfgOptionPtr&
getCfgOption() const131 ClientClassDef::getCfgOption() const {
132     return (cfg_option_);
133 }
134 
135 void
setCfgOption(const CfgOptionPtr & cfg_option)136 ClientClassDef::setCfgOption(const CfgOptionPtr& cfg_option) {
137     cfg_option_ = cfg_option;
138 }
139 
140 bool
dependOnClass(const std::string & name) const141 ClientClassDef::dependOnClass(const std::string& name) const {
142     return (isc::dhcp::dependOnClass(match_expr_, name));
143 }
144 
145 bool
equals(const ClientClassDef & other) const146 ClientClassDef::equals(const ClientClassDef& other) const {
147     return ((name_ == other.name_) &&
148         ((!match_expr_ && !other.match_expr_) ||
149         (match_expr_ && other.match_expr_ &&
150          (*match_expr_ == *(other.match_expr_)))) &&
151         ((!cfg_option_ && !other.cfg_option_) ||
152         (cfg_option_ && other.cfg_option_ &&
153          (*cfg_option_ == *other.cfg_option_))) &&
154         ((!cfg_option_def_ && !other.cfg_option_def_) ||
155         (cfg_option_def_ && other.cfg_option_def_ &&
156          (*cfg_option_def_ == *other.cfg_option_def_))) &&
157             (required_ == other.required_) &&
158             (depend_on_known_ == other.depend_on_known_) &&
159             (next_server_ == other.next_server_) &&
160             (sname_ == other.sname_) &&
161             (filename_ == other.filename_));
162 }
163 
164 ElementPtr
toElement() const165 ClientClassDef:: toElement() const {
166     uint16_t family = CfgMgr::instance().getFamily();
167     ElementPtr result = Element::createMap();
168     // Set user-context
169     contextToElement(result);
170     // Set name
171     result->set("name", Element::create(name_));
172     // Set original match expression (empty string won't parse)
173     if (!test_.empty()) {
174         result->set("test", Element::create(test_));
175     }
176     // Set only-if-required
177     if (required_) {
178         result->set("only-if-required", Element::create(required_));
179     }
180     // Set option-def (used only by DHCPv4)
181     if (cfg_option_def_ && (family == AF_INET)) {
182         result->set("option-def", cfg_option_def_->toElement());
183     }
184     // Set option-data
185     result->set("option-data", cfg_option_->toElement());
186 
187     if (family == AF_INET) {
188         // V4 only
189         // Set next-server
190         result->set("next-server", Element::create(next_server_.toText()));
191         // Set server-hostname
192         result->set("server-hostname", Element::create(sname_));
193         // Set boot-file-name
194         result->set("boot-file-name", Element::create(filename_));
195     } else {
196         // V6 only
197         // Set preferred-lifetime
198         if (!preferred_.unspecified()) {
199             result->set("preferred-lifetime",
200                         Element::create(static_cast<long long>(preferred_.get())));
201         }
202 
203         if (preferred_.getMin() < preferred_.get()) {
204             result->set("min-preferred-lifetime",
205                         Element::create(static_cast<long long>(preferred_.getMin())));
206         }
207 
208         if (preferred_.getMax() > preferred_.get()) {
209             result->set("max-preferred-lifetime",
210                         Element::create(static_cast<long long>(preferred_.getMax())));
211         }
212     }
213 
214     // Set valid-lifetime
215     if (!valid_.unspecified()) {
216         result->set("valid-lifetime",
217                     Element::create(static_cast<long long>(valid_.get())));
218 
219         if (valid_.getMin() < valid_.get()) {
220             result->set("min-valid-lifetime",
221                         Element::create(static_cast<long long>(valid_.getMin())));
222         }
223 
224         if (valid_.getMax() > valid_.get()) {
225             result->set("max-valid-lifetime",
226                         Element::create(static_cast<long long>(valid_.getMax())));
227         }
228     }
229 
230     return (result);
231 }
232 
operator <<(std::ostream & os,const ClientClassDef & x)233 std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
234     os << "ClientClassDef:" << x.getName();
235     return (os);
236 }
237 
238 //********** ClientClassDictionary ******************//
239 
ClientClassDictionary()240 ClientClassDictionary::ClientClassDictionary()
241     : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
242 }
243 
ClientClassDictionary(const ClientClassDictionary & rhs)244 ClientClassDictionary::ClientClassDictionary(const ClientClassDictionary& rhs)
245     : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
246     BOOST_FOREACH(ClientClassDefPtr cclass, *(rhs.list_)) {
247         ClientClassDefPtr copy(new ClientClassDef(*cclass));
248         addClass(copy);
249     }
250 }
251 
~ClientClassDictionary()252 ClientClassDictionary::~ClientClassDictionary() {
253 }
254 
255 void
addClass(const std::string & name,const ExpressionPtr & match_expr,const std::string & test,bool required,bool depend_on_known,const CfgOptionPtr & cfg_option,CfgOptionDefPtr cfg_option_def,ConstElementPtr user_context,asiolink::IOAddress next_server,const std::string & sname,const std::string & filename,const Triplet<uint32_t> & valid,const Triplet<uint32_t> & preferred)256 ClientClassDictionary::addClass(const std::string& name,
257                                 const ExpressionPtr& match_expr,
258                                 const std::string& test,
259                                 bool required,
260                                 bool depend_on_known,
261                                 const CfgOptionPtr& cfg_option,
262                                 CfgOptionDefPtr cfg_option_def,
263                                 ConstElementPtr user_context,
264                                 asiolink::IOAddress next_server,
265                                 const std::string& sname,
266                                 const std::string& filename,
267                                 const Triplet<uint32_t>& valid,
268                                 const Triplet<uint32_t>& preferred) {
269     ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
270     cclass->setTest(test);
271     cclass->setRequired(required);
272     cclass->setDependOnKnown(depend_on_known);
273     cclass->setCfgOptionDef(cfg_option_def);
274     cclass->setContext(user_context),
275     cclass->setNextServer(next_server);
276     cclass->setSname(sname);
277     cclass->setFilename(filename);
278     cclass->setValid(valid);
279     cclass->setPreferred(preferred);
280     addClass(cclass);
281 }
282 
283 void
addClass(ClientClassDefPtr & class_def)284 ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
285     if (!class_def) {
286         isc_throw(BadValue, "ClientClassDictionary::addClass "
287                             " - class definition cannot be null");
288     }
289 
290     if (findClass(class_def->getName())) {
291         isc_throw(DuplicateClientClassDef, "Client Class: "
292                   << class_def->getName() << " has already been defined");
293     }
294 
295     list_->push_back(class_def);
296     (*map_)[class_def->getName()] = class_def;
297 }
298 
299 ClientClassDefPtr
findClass(const std::string & name) const300 ClientClassDictionary::findClass(const std::string& name) const {
301     ClientClassDefMap::iterator it = map_->find(name);
302     if (it != map_->end()) {
303         return (*it).second;
304     }
305 
306     return (ClientClassDefPtr());
307 }
308 
309 void
removeClass(const std::string & name)310 ClientClassDictionary::removeClass(const std::string& name) {
311     for (ClientClassDefList::iterator this_class = list_->begin();
312          this_class != list_->end(); ++this_class) {
313         if ((*this_class)->getName() == name) {
314             list_->erase(this_class);
315             break;
316         }
317     }
318     map_->erase(name);
319 }
320 
321 void
removeClass(const uint64_t id)322 ClientClassDictionary::removeClass(const uint64_t id) {
323     // Class id equal to 0 means it wasn't set.
324     if (id == 0) {
325         return;
326     }
327     for (ClientClassDefList::iterator this_class = list_->begin();
328          this_class != list_->end(); ++this_class) {
329         if ((*this_class)->getId() == id) {
330             map_->erase((*this_class)->getName());
331             list_->erase(this_class);
332             break;
333         }
334     }
335 }
336 
337 const ClientClassDefListPtr&
getClasses() const338 ClientClassDictionary::getClasses() const {
339     return (list_);
340 }
341 
342 bool
empty() const343 ClientClassDictionary::empty() const {
344     return (list_->empty());
345 }
346 
347 bool
dependOnClass(const std::string & name,std::string & dependent_class) const348 ClientClassDictionary::dependOnClass(const std::string& name,
349                                      std::string& dependent_class) const {
350     // Skip previous classes as they should not depend on name.
351     bool found = false;
352     for (ClientClassDefList::iterator this_class = list_->begin();
353          this_class != list_->end(); ++this_class) {
354         if (found) {
355             if ((*this_class)->dependOnClass(name)) {
356                 dependent_class = (*this_class)->getName();
357                 return (true);
358             }
359         } else {
360             if ((*this_class)->getName() == name) {
361                 found = true;
362             }
363         }
364     }
365     return (false);
366 }
367 
368 bool
equals(const ClientClassDictionary & other) const369 ClientClassDictionary::equals(const ClientClassDictionary& other) const {
370     if (list_->size() != other.list_->size()) {
371         return (false);
372     }
373 
374     ClientClassDefList::const_iterator this_class = list_->cbegin();
375     ClientClassDefList::const_iterator other_class = other.list_->cbegin();
376     while (this_class != list_->cend() &&
377            other_class != other.list_->cend()) {
378         if (!*this_class || !*other_class ||
379             **this_class != **other_class) {
380                 return false;
381         }
382 
383         ++this_class;
384         ++other_class;
385     }
386 
387     return (true);
388 }
389 
390 void
initMatchExpr(uint16_t family)391 ClientClassDictionary::initMatchExpr(uint16_t family) {
392     std::queue<ExpressionPtr> expressions;
393     for (auto c : *list_) {
394         ExpressionPtr match_expr = boost::make_shared<Expression>();
395         if (!c->getTest().empty()) {
396             ExpressionParser parser;
397             parser.parse(match_expr, Element::create(c->getTest()), family);
398         }
399         expressions.push(match_expr);
400     }
401     // All expressions successfully initialized. Let's set them for the
402     // client classes in the dictionary.
403     for (auto c : *list_) {
404         c->setMatchExpr(expressions.front());
405         expressions.pop();
406     }
407 }
408 
409 ElementPtr
toElement() const410 ClientClassDictionary::toElement() const {
411     ElementPtr result = Element::createList();
412     // Iterate on the map
413     for (ClientClassDefList::const_iterator this_class = list_->begin();
414          this_class != list_->cend(); ++this_class) {
415         result->add((*this_class)->toElement());
416     }
417     return (result);
418 }
419 
420 ClientClassDictionary&
operator =(const ClientClassDictionary & rhs)421 ClientClassDictionary::operator=(const ClientClassDictionary& rhs) {
422     if (this != &rhs) {
423         list_->clear();
424         map_->clear();
425         for (auto cclass : *(rhs.list_)) {
426             ClientClassDefPtr copy(new ClientClassDef(*cclass));
427             addClass(copy);
428         }
429     }
430     return (*this);
431 }
432 
433 std::list<std::string>
434 builtinNames = {
435     // DROP is not in this list because it is special but not built-in.
436     // In fact DROP is set from an expression as callouts can drop
437     // directly the incoming packet. The expression must not depend on
438     // KNOWN/UNKNOWN which are set far after the drop point.
439     "ALL", "KNOWN", "UNKNOWN", "BOOTP"
440 };
441 
442 std::list<std::string>
443 builtinPrefixes = {
444     "VENDOR_CLASS_", "HA_", "AFTER_", "EXTERNAL_"
445 };
446 
447 bool
isClientClassBuiltIn(const ClientClass & client_class)448 isClientClassBuiltIn(const ClientClass& client_class) {
449     for (std::list<std::string>::const_iterator bn = builtinNames.cbegin();
450          bn != builtinNames.cend(); ++bn) {
451         if (client_class == *bn) {
452             return true;
453         }
454     }
455 
456     for (std::list<std::string>::const_iterator bt = builtinPrefixes.cbegin();
457          bt != builtinPrefixes.cend(); ++bt) {
458         if (client_class.size() <= bt->size()) {
459             continue;
460         }
461         auto mis = std::mismatch(bt->cbegin(), bt->cend(), client_class.cbegin());
462         if (mis.first == bt->cend()) {
463             return true;
464         }
465     }
466 
467     return false;
468 }
469 
470 bool
isClientClassDefined(ClientClassDictionaryPtr & class_dictionary,bool & depend_on_known,const ClientClass & client_class)471 isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
472                      bool& depend_on_known,
473                      const ClientClass& client_class) {
474     // First check built-in classes
475     if (isClientClassBuiltIn(client_class)) {
476         // Check direct dependency on [UN]KNOWN
477         if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) {
478             depend_on_known = true;
479         }
480         return (true);
481     }
482 
483     // Second check already defined, i.e. in the dictionary
484     ClientClassDefPtr def = class_dictionary->findClass(client_class);
485     if (def) {
486         // Check indirect dependency on [UN]KNOWN
487         if (def->getDependOnKnown()) {
488             depend_on_known = true;
489         }
490         return (true);
491     }
492 
493     // Not defined...
494     return (false);
495 }
496 
497 } // namespace isc::dhcp
498 } // namespace isc
499