Branch data Line data Source code
1 : : // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : #ifndef ACL_LOADER_H
16 : : #define ACL_LOADER_H
17 : :
18 : : #include <exceptions/exceptions.h>
19 : : #include <acl/acl.h>
20 : : #include <cc/data.h>
21 : : #include <boost/function.hpp>
22 : : #include <boost/shared_ptr.hpp>
23 : : #include <map>
24 : :
25 : : namespace isc {
26 : : namespace acl {
27 : :
28 : : class AnyOfSpec;
29 : : class AllOfSpec;
30 : : template<typename Mode, typename Context> class LogicOperator;
31 : :
32 : : /**
33 : : * \brief Exception for bad ACL specifications.
34 : : *
35 : : * This will be thrown by the Loader if the ACL description is malformed
36 : : * in some way.
37 : : *
38 : : * It also can hold optional JSON element where was the error detected, so
39 : : * it can be examined.
40 : : *
41 : : * Checks may subclass this exception for similar errors if they see it fit.
42 : : */
43 : : class LoaderError : public BadValue {
44 : : private:
45 : : const data::ConstElementPtr element_;
46 : : public:
47 : : /**
48 : : * \brief Constructor.
49 : : *
50 : : * Should be used with isc_throw if the fourth argument isn't used.
51 : : *
52 : : * \param file The file where the throw happened.
53 : : * \param line Similar as file, just for the line number.
54 : : * \param what Human readable description of what happened.
55 : : * \param element This might be passed to hold the JSON element where
56 : : * the error was detected.
57 : : */
58 : 85 : LoaderError(const char* file, size_t line, const char* what,
59 : : data::ConstElementPtr element = data::ConstElementPtr()) :
60 : : BadValue(file, line, what),
61 : 170 : element_(element)
62 : 85 : {}
63 : :
64 [ # # ]: 85 : ~ LoaderError() throw() {}
65 : :
66 : : /**
67 : : * \brief Get the element.
68 : : *
69 : : * This returns the element where the error was detected. Note that it
70 : : * might be NULL in some situations.
71 : : */
72 : : const data::ConstElementPtr& element() const {
73 : : return (element_);
74 : : }
75 : : };
76 : :
77 : : /**
78 : : * \brief Loader of the default actions of ACLs.
79 : : *
80 : : * Declared outside the Loader class, as this one does not need to be
81 : : * templated. This will throw LoaderError if the parameter isn't string
82 : : * or if it doesn't contain one of the accepted values.
83 : : *
84 : : * \param action The JSON representation of the action. It must be a string
85 : : * and contain one of "ACCEPT", "REJECT" or "DROP.
86 : : * \note We could define different names or add aliases if needed.
87 : : */
88 : : BasicAction defaultActionLoader(data::ConstElementPtr action);
89 : :
90 : : /**
91 : : * \brief Loader of ACLs.
92 : : *
93 : : * The goal of this class is to convert JSON description of an ACL to object
94 : : * of the ACL class (including the checks inside it).
95 : : *
96 : : * The class can be used to load the checks only. This is supposed to be used
97 : : * by compound checks to create the subexpressions.
98 : : *
99 : : * To allow any kind of checks to exist in the application, creators are
100 : : * registered for the names of the checks.
101 : : *
102 : : * An ACL definition looks like this:
103 : : * \verbatim
104 : : [
105 : : {
106 : : "action": "ACCEPT",
107 : : "match-type": <parameter>
108 : : },
109 : : {
110 : : "action": "REJECT",
111 : : "match-type": <parameter>,
112 : : "another-match-type": [<parameter1>, <parameter2>]
113 : : },
114 : : {
115 : : "action": "DROP"
116 : : }
117 : : ]
118 : : \endverbatim
119 : : *
120 : : * This is a list of elements. Each element must have an "action"
121 : : * entry/keyword. That one specifies which action is returned if this
122 : : * element matches (the value of the key is passed to the action loader
123 : : * (see the constructor). It may be any piece of JSON which the action
124 : : * loader expects.
125 : : *
126 : : * The rest of the element are matches. The left side is the name of the
127 : : * match type (for example match for source IP address or match for message
128 : : * size). The parameter is whatever is needed to describe the match and
129 : : * depends on the match type, the loader passes it verbatim to creator
130 : : * of that match type.
131 : : *
132 : : * There may be multiple match types in single element. In such case, all
133 : : * of the matches must match for the element to take action (so, in the second
134 : : * element, both "match-type" and "another-match-type" must be satisfied).
135 : : * If there's no match in the element, the action is taken/returned without
136 : : * conditions, every time (makes sense as the last entry, as the ACL will
137 : : * never get past it).
138 : : *
139 : : * The second entry shows another thing - if there's a list as the value
140 : : * for some match and the match itself is not expecting a list, it is taken
141 : : * as an "or" - a match for at last one of the choices in the list must match.
142 : : * So, for the second entry, both "match-type" and "another-match-type" must
143 : : * be satisfied, but the another one is satisfied by either parameter1 or
144 : : * parameter2.
145 : : */
146 [ + - ]: 64 : template<typename Context, typename Action = BasicAction> class Loader {
147 : : public:
148 : : /**
149 : : * \brief Constructor.
150 : : *
151 : : * \param defaultAction The default action for created ACLs.
152 : : * \param actionLoader is the loader which will be used to convert actions
153 : : * from their JSON representation. The default value is suitable for
154 : : * the BasicAction enum. If you did not specify the second
155 : : * template argument, you don't need to specify this loader.
156 : : */
157 : 0 : Loader(const Action& defaultAction,
158 : : const boost::function1<Action, data::ConstElementPtr>
159 : : &actionLoader = &defaultActionLoader) :
160 : : default_action_(defaultAction),
161 : 72 : action_loader_(actionLoader)
162 : 0 : {}
163 : :
164 : : /**
165 : : * \brief Creator of the checks.
166 : : *
167 : : * This can be registered within the Loader and will be used to create the
168 : : * checks. It is expected multiple creators (for multiple types, one can
169 : : * handle even multiple names) will be created and registered to support
170 : : * range of things we could check. This allows for customizing/extending
171 : : * the loader.
172 : : */
173 : 123 : class CheckCreator {
174 : : public:
175 : : /** \brief Virtual class needs virtual destructor */
176 : 107 : virtual ~CheckCreator() {}
177 : :
178 : : /**
179 : : * \brief List of names supported by this loader.
180 : : *
181 : : * List of all names for which this loader is able to create the
182 : : * checks. There can be multiple names, to support both aliases
183 : : * to the same checks and creators capable of creating multiple
184 : : * types of checks.
185 : : */
186 : : virtual std::vector<std::string> names() const = 0;
187 : :
188 : : /**
189 : : * \brief Creates the check.
190 : : *
191 : : * This function does the actual creation. It is passed all the
192 : : * relevant data and is supposed to return shared pointer to the
193 : : * check.
194 : : *
195 : : * It is expected to throw the LoaderError exception when the
196 : : * definition is invalid.
197 : : *
198 : : * \param name The type name of the check. If the creator creates
199 : : * only one type of check, it can safely ignore this parameter.
200 : : * \param definition The part of JSON describing the parameters of
201 : : * check. As there's no way for the loader to know how the
202 : : * parameters might look like, they are not checked in any way.
203 : : * Therefore it's up to the creator (or the check being created)
204 : : * to validate the data and throw if it is bad.
205 : : * \param loader Current loader calling this creator. This can be used
206 : : * to load subexpressions in case of compound check.
207 : : */
208 : : virtual boost::shared_ptr<Check<Context> > create(
209 : : const std::string& name, data::ConstElementPtr definition,
210 : : const Loader<Context, Action>& loader) = 0;
211 : :
212 : : /**
213 : : * \brief Is list or-abbreviation allowed?
214 : : *
215 : : * If this returns true and the parameter (eg. the value we check
216 : : * against, the one that is passed as the second parameter of create)
217 : : * is list, the loader will call the create method with each element of
218 : : * the list and aggregate all the results in OR compound check. If it
219 : : * is false, the parameter is passed verbatim no matter if it is or
220 : : * isn't a list. For example, IP check will have this as true (so
221 : : * multiple IP addresses can be passed as options), but AND operator
222 : : * will return false and handle the list of subexpressions itself.
223 : : *
224 : : * The rationale behind this is that it is common to specify list of
225 : : * something that matches (eg. list of IP addresses).
226 : : */
227 : 5 : virtual bool allowListAbbreviation() const {
228 : 5 : return (true);
229 : : }
230 : : };
231 : :
232 : : /**
233 : : * \brief Register another check creator.
234 : : *
235 : : * Adds a creator to the list of known ones. The creator's list of names
236 : : * must be disjoint with the names already known to the creator or the
237 : : * LoaderError exception is thrown. In such case, the creator is not
238 : : * registered under any of the names. In case of other exceptions, like
239 : : * bad_alloc, only weak exception safety is guaranteed.
240 : : *
241 : : * \param creator Shared pointer to the creator.
242 : : * \note We don't support deregistration yet, but it is expected it will
243 : : * be needed in future, when we have some kind of plugins. These
244 : : * plugins might want to unload, in which case they would need to
245 : : * deregister their creators. It is expected they would pass the same
246 : : * pointer to such method as they pass here.
247 : : */
248 : 114 : void registerCreator(boost::shared_ptr<CheckCreator> creator) {
249 : : // First check we can insert all the names
250 : : typedef std::vector<std::string> Strings;
251 : 228 : const Strings names(creator->names());
252 [ + + ]: 222 : for (Strings::const_iterator i(names.begin()); i != names.end();
253 : : ++i) {
254 [ + + ]: 120 : if (creators_.find(*i) != creators_.end()) {
255 [ + - ][ + - ]: 36 : isc_throw(LoaderError, "The loader already contains creator "
[ + - ][ + - ]
256 : : "named " << *i);
257 : : }
258 : : }
259 : : // Now insert them
260 [ + + ]: 209 : for (Strings::const_iterator i(names.begin()); i != names.end();
261 : : ++i) {
262 [ + - ]: 107 : creators_[*i] = creator;
263 : : }
264 : 102 : }
265 : :
266 : : /**
267 : : * \brief Load a check.
268 : : *
269 : : * This parses a check dict (block, the one element of ACL) and calls a
270 : : * creator (or creators, if more than one check is found inside) for it. It
271 : : * ignores the "action" key, as it is a reserved keyword used to specify
272 : : * actions inside the ACL.
273 : : *
274 : : * This may throw LoaderError if it is not a dict or if some of the type
275 : : * names is not known (there's no creator registered for it). The
276 : : * exceptions from creators aren't caught.
277 : : *
278 : : * \param description The JSON description of the check.
279 : : */
280 : 80 : boost::shared_ptr<Check<Context> > loadCheck(const data::ConstElementPtr&
281 : : description) const
282 : : {
283 : : // Get the description as a map
284 : : typedef std::map<std::string, data::ConstElementPtr> Map;
285 : 80 : Map map;
286 : : try {
287 [ + + ]: 80 : map = description->mapValue();
288 : : }
289 [ - + ]: 20 : catch (const data::TypeError&) {
290 [ - + ][ - + ]: 30 : isc_throw_1(LoaderError, "Check description is not a map",
[ - + ]
291 : : description);
292 : : }
293 : : // Call the internal part with extracted map
294 [ + + ]: 115 : return (loadCheck(description, map));
295 : : }
296 : :
297 : : /**
298 : : * \brief Load an ACL.
299 : : *
300 : : * This parses an ACL list, creates the checks and actions of each element
301 : : * and returns it.
302 : : *
303 : : * No exceptions from \c loadCheck (therefore from whatever creator is
304 : : * used) and from the actionLoader passed to constructor are caught.
305 : : *
306 : : * \exception InvalidParameter The given element is NULL (most likely a
307 : : * caller's bug)
308 : : * \exception LoaderError The given element isn't a list or the
309 : : * "action" key is missing in some element
310 : : *
311 : : * \param description The JSON list of ACL.
312 : : *
313 : : * \return The newly created ACL object
314 : : */
315 : 254 : boost::shared_ptr<ACL<Context, Action> > load(const data::ConstElementPtr&
316 : : description) const
317 : : {
318 [ + + ]: 254 : if (!description) {
319 [ + - ]: 2 : isc_throw(isc::InvalidParameter,
320 : : "Null description is passed to ACL loader");
321 : : }
322 : :
323 : : // We first check it's a list, so we can use the list reference
324 : : // (the list may be huge)
325 [ + + ]: 253 : if (description->getType() != data::Element::list) {
326 [ + - ][ + - ]: 45 : isc_throw_1(LoaderError, "ACL not a list", description);
327 : : }
328 : : // First create an empty ACL
329 : 238 : const List &list(description->listValue());
330 : : boost::shared_ptr<ACL<Context, Action> > result(
331 : 238 : new ACL<Context, Action>(default_action_));
332 : : // Run trough the list of elements
333 [ + + ]: 401 : for (List::const_iterator i(list.begin()); i != list.end(); ++i) {
334 [ + - ]: 226 : Map map;
335 : : try {
336 [ + + ]: 226 : map = (*i)->mapValue();
337 : : }
338 [ - + ]: 32 : catch (const data::TypeError&) {
339 [ - + ][ - + ]: 48 : isc_throw_1(LoaderError, "ACL element not a map", *i);
[ - + ]
340 : : }
341 : : // Create an action for the element
342 [ + - ]: 420 : const Map::const_iterator action(map.find("action"));
343 [ + + ]: 210 : if (action == map.end()) {
344 [ + - ][ + - ]: 15 : isc_throw_1(LoaderError, "No action in ACL element", *i);
[ + - ]
345 : : }
346 [ + + ]: 205 : const Action acValue(action_loader_(action->second));
347 : : // Now create the check if there's one
348 [ + + ]: 202 : if (map.size() >= 2) { // One is the action, another one the check
349 [ + + ]: 227 : result->append(loadCheck(*i, map), acValue);
[ + - + ]
350 : : } else {
351 : : // In case there's no check, this matches every time. We
352 : : // simulate it by our own private "True" check.
353 [ + - ]: 138 : result->append(boost::shared_ptr<Check<Context> >(new True()),
[ + - + ]
354 : : acValue);
355 : : }
356 : : }
357 : 175 : return (result);
358 : : }
359 : :
360 : : private:
361 : : // Some type aliases to save typing
362 : : typedef std::map<std::string, boost::shared_ptr<CheckCreator> > Creators;
363 : : typedef std::map<std::string, data::ConstElementPtr> Map;
364 : : typedef std::vector<data::ConstElementPtr> List;
365 : : // Private members
366 : : Creators creators_;
367 : : const Action default_action_;
368 : : const boost::function1<Action, data::ConstElementPtr> action_loader_;
369 : :
370 : : /**
371 : : * \brief Internal version of loadCheck.
372 : : *
373 : : * This is the internal part, shared between load and loadCheck.
374 : : * \param description The bit of JSON (used in exceptions).
375 : : * \param map The extracted map describing the check. It does change
376 : : * the map.
377 : : */
378 : 207 : boost::shared_ptr<Check<Context> > loadCheck(const data::ConstElementPtr&
379 : : description, Map& map) const
380 : : {
381 : : // Remove the action keyword
382 : 414 : map.erase("action");
383 : : // Now, do we have any definition? Or is it and abbreviation?
384 [ - + - ]: 140 : switch (map.size()) {
385 : : case 0:
386 [ + - ][ + - ]: 3 : isc_throw_1(LoaderError, "Check description is empty",
387 : : description);
388 : : case 1: {
389 : : // Get the first and only item
390 : 204 : const Map::const_iterator checkDesc(map.begin());
391 : 204 : const std::string& name(checkDesc->first);
392 : : const typename Creators::const_iterator
393 : 408 : creatorIt(creators_.find(name));
394 [ + + ]: 204 : if (creatorIt == creators_.end()) {
395 [ + - ][ + - ]: 15 : isc_throw_1(LoaderError, "No creator for ACL check " <<
[ + - ]
396 : : name, description);
397 : : }
398 [ + + ][ + + ]: 199 : if (creatorIt->second->allowListAbbreviation() &&
[ + + ]
399 : : checkDesc->second->getType() == data::Element::list) {
400 : : // Or-abbreviated form - create an OR and put everything
401 : : // inside.
402 : : const std::vector<data::ConstElementPtr>&
403 : 2 : params(checkDesc->second->listValue());
404 : : boost::shared_ptr<LogicOperator<AnyOfSpec, Context> >
405 : 2 : oper(new LogicOperator<AnyOfSpec, Context>);
406 [ + + ]: 6 : for (std::vector<data::ConstElementPtr>::const_iterator
407 : 2 : i(params.begin());
408 : : i != params.end(); ++i) {
409 [ + - ]: 8 : oper->addSubexpression(
410 : : creatorIt->second->create(name, *i, *this));
411 : : }
412 : : return (oper);
413 : : }
414 : : // Create the check and return it
415 : : return (creatorIt->second->create(name, checkDesc->second,
416 [ + + ]: 394 : *this));
417 : : }
418 : : default: {
419 : : // This is the AND-abbreviated form. We need to create an
420 : : // AND (or "ALL") operator, loop trough the whole map and
421 : : // fill it in. We do a small trick - we create bunch of
422 : : // single-item maps, call this loader recursively (therefore
423 : : // it will get into the "case 1" branch, where there is
424 : : // the actual loading) and use the results to fill the map.
425 : : //
426 : : // We keep the description the same, there's nothing we could
427 : : // take out (we could create a new one, but that would be
428 : : // confusing, as it is used for error messages only).
429 : : boost::shared_ptr<LogicOperator<AllOfSpec, Context> >
430 : 2 : oper(new LogicOperator<AllOfSpec, Context>);
431 [ + + ]: 6 : for (Map::const_iterator i(map.begin()); i != map.end(); ++i) {
432 [ + - ]: 4 : Map singleSubexpr;
433 : 4 : singleSubexpr.insert(*i);
434 [ + - ]: 4 : oper->addSubexpression(loadCheck(description,
435 : : singleSubexpr));
436 : : }
437 : : return (oper);
438 : : }
439 : : }
440 : : }
441 : :
442 : : /**
443 : : * \brief Check that always matches.
444 : : *
445 : : * This one is used internally for ACL elements without condition. We may
446 : : * want to make this publicly accesible sometime maybe, but for now,
447 : : * there's no need.
448 : : */
449 : 69 : class True : public Check<Context> {
450 : : public:
451 : 33 : virtual bool matches(const Context&) const { return (true); };
452 : 0 : virtual unsigned cost() const { return (1); }
453 : : // We don't write "true" here, as this one was created using empty
454 : : // input
455 : 0 : virtual std::string toText() const { return ""; }
456 : : };
457 : : };
458 : :
459 : : }
460 : : }
461 : :
462 : : /*
463 : : * This include at the end of the file is unusual. But we need to include it,
464 : : * we use template classes from there. However, they need to be present only
465 : : * at instantiation of our class, which will happen below this header.
466 : : *
467 : : * The problem is, the header uses us as well, therefore there's a circular
468 : : * dependency. If we loaded it at the beginning and someone loaded us first,
469 : : * the logic_check header wouldn't have our definitions. This way, no matter
470 : : * in which order they are loaded, the definitions from this header will be
471 : : * above the ones from logic_check.
472 : : */
473 : : #include "logic_check.h"
474 : :
475 : : #endif
476 : :
477 : : // Local Variables:
478 : : // mode: c++
479 : : // End:
|