Branch data Line data Source code
1 : : // Copyright (C) 2010 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 : : #include <dns/name.h>
16 : : #include <dns/rrclass.h>
17 : :
18 : : #include <cc/data.h>
19 : :
20 : : #include <datasrc/memory_datasrc.h>
21 : : #include <datasrc/zonetable.h>
22 : : #include <datasrc/factory.h>
23 : :
24 : : #include <auth/auth_srv.h>
25 : : #include <auth/auth_config.h>
26 : : #include <auth/common.h>
27 : :
28 : : #include <server_common/portconfig.h>
29 : :
30 : : #include <boost/foreach.hpp>
31 : : #include <boost/shared_ptr.hpp>
32 : : #include <boost/scoped_ptr.hpp>
33 : :
34 : : #include <set>
35 : : #include <string>
36 : : #include <utility>
37 : : #include <vector>
38 : :
39 : : using namespace std;
40 : : using namespace isc::dns;
41 : : using namespace isc::data;
42 : : using namespace isc::datasrc;
43 : : using namespace isc::server_common::portconfig;
44 : :
45 : : namespace {
46 : : // Forward declaration
47 : : AuthConfigParser*
48 : : createAuthConfigParser(AuthSrv& server, const std::string& config_id,
49 : : bool internal);
50 : :
51 : : /// A derived \c AuthConfigParser class for the "datasources" configuration
52 : : /// identifier.
53 [ + - ]: 117 : class DatasourcesConfig : public AuthConfigParser {
54 : : public:
55 : 117 : DatasourcesConfig(AuthSrv& server) : server_(server) {}
56 : : virtual void build(ConstElementPtr config_value);
57 : : virtual void commit();
58 : : private:
59 : : AuthSrv& server_;
60 : : vector<boost::shared_ptr<AuthConfigParser> > datasources_;
61 : : set<string> configured_sources_;
62 : : };
63 : :
64 : : /// A derived \c AuthConfigParser for the version value
65 : : /// (which is not used at this moment)
66 : 1 : class VersionConfig : public AuthConfigParser {
67 : : public:
68 : 2 : VersionConfig() {}
69 : 1 : virtual void build(ConstElementPtr) {};
70 : 1 : virtual void commit() {};
71 : : };
72 : :
73 : : void
74 : 48 : DatasourcesConfig::build(ConstElementPtr config_value) {
75 [ + + + - ]: 191 : BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
[ + - ][ + + ]
[ + + ]
76 : : // The caller is supposed to perform syntax-level checks, but we'll
77 : : // do minimum level of validation ourselves so that we won't crash due
78 : : // to a buggy application.
79 [ + - ][ + - ]: 47 : ConstElementPtr datasrc_type = datasrc_elem->get("type");
80 [ + + ]: 47 : if (!datasrc_type) {
81 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError, "Missing data source type");
[ + - ]
82 : : }
83 : :
84 [ + + ][ + + ]: 91 : if (configured_sources_.find(datasrc_type->stringValue()) !=
85 : 91 : configured_sources_.end()) {
86 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError, "Data source type '" <<
[ + - ][ + - ]
[ + - ][ + - ]
87 : : datasrc_type->stringValue() << "' already configured");
88 : : }
89 : :
90 : : boost::shared_ptr<AuthConfigParser> datasrc_config =
91 : : boost::shared_ptr<AuthConfigParser>(
92 : 44 : createAuthConfigParser(server_, string("datasources/") +
93 : 132 : datasrc_type->stringValue(),
94 [ + - ][ + - ]: 88 : true));
[ + + ][ + - ]
95 [ + + ]: 86 : datasrc_config->build(datasrc_elem);
96 [ + - ]: 32 : datasources_.push_back(datasrc_config);
97 : :
98 [ + - ]: 64 : configured_sources_.insert(datasrc_type->stringValue());
99 : : }
100 : 33 : }
101 : :
102 : : void
103 : 32 : DatasourcesConfig::commit() {
104 : : // XXX a short term workaround: clear all data sources and then reset
105 : : // to new ones so that we can remove data sources that don't exist in
106 : : // the new configuration and have been used in the server.
107 : : // This could be inefficient and requires knowledge about
108 : : // server implementation details, and isn't scalable wrt the number of
109 : : // data source types, and should eventually be improved.
110 : : // Currently memory data source for class IN is the only possibility.
111 [ + - ]: 32 : server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
112 : :
113 [ + + + - ]: 152 : BOOST_FOREACH(boost::shared_ptr<AuthConfigParser> datasrc_config,
[ + - ][ + + ]
[ + + ]
114 : : datasources_) {
115 [ + - ]: 30 : datasrc_config->commit();
116 : : }
117 : 32 : }
118 : :
119 : : /// A derived \c AuthConfigParser class for the memory type datasource
120 : : /// configuration. It does not correspond to the configuration syntax;
121 : : /// it's instantiated for internal use.
122 : 86 : class MemoryDatasourceConfig : public AuthConfigParser {
123 : : public:
124 : : MemoryDatasourceConfig(AuthSrv& server) :
125 : : server_(server),
126 : 129 : rrclass_(0) // XXX: dummy initial value
127 : : {}
128 : : virtual void build(ConstElementPtr config_value);
129 : 30 : virtual void commit() {
130 [ + - ]: 30 : server_.setInMemoryClient(rrclass_, memory_client_);
131 : 30 : }
132 : : private:
133 : : AuthSrv& server_;
134 : : RRClass rrclass_;
135 : : AuthSrv::InMemoryClientPtr memory_client_;
136 : : };
137 : :
138 : : void
139 : 43 : MemoryDatasourceConfig::build(ConstElementPtr config_value) {
140 : : // XXX: apparently we cannot retrieve the default RR class from the
141 : : // module spec. As a temporary workaround we hardcode the default value.
142 [ + - ]: 43 : ConstElementPtr rrclass_elem = config_value->get("class");
143 [ + + ][ + - ]: 43 : rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
[ + - ][ + + ]
144 : :
145 : : // We'd eventually optimize building zones (in case of reloading) by
146 : : // selectively loading fresh zones. Right now we simply check the
147 : : // RR class is supported by the server implementation.
148 [ + + ]: 41 : server_.getInMemoryClient(rrclass_);
149 [ + - ][ + - ]: 40 : memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
150 : :
151 [ + - ][ + - ]: 40 : ConstElementPtr zones_config = config_value->get("zones");
152 [ + + ]: 40 : if (!zones_config) {
153 : : // XXX: Like the RR class, we cannot retrieve the default value here,
154 : : // so we assume an empty zone list in this case.
155 : 32 : return;
156 : : }
157 : :
158 [ + - ][ + + ]: 178 : BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
[ + - ][ + - ]
[ + + ][ + + ]
159 [ + - ][ + - ]: 42 : ConstElementPtr origin = zone_config->get("origin");
160 [ + + ][ + - ]: 84 : const string origin_txt = origin ? origin->stringValue() : "";
[ + - ]
161 [ + + ]: 42 : if (origin_txt.empty()) {
162 [ + - ][ + - ]: 4 : isc_throw(AuthConfigError, "Missing zone origin");
[ + - ]
163 : : }
164 [ + - ][ + - ]: 40 : ConstElementPtr file = zone_config->get("file");
165 [ + + ][ + - ]: 80 : const string file_txt = file ? file->stringValue() : "";
[ + - ]
166 [ + + ]: 40 : if (file_txt.empty()) {
167 [ + - ][ + - ]: 4 : isc_throw(AuthConfigError, "Missing zone file for zone: "
[ + - ][ + - ]
168 : : << origin_txt);
169 : : }
170 : :
171 : : // We support the traditional text type and SQLite3 backend. For the
172 : : // latter we create a client for the underlying SQLite3 data source,
173 : : // and build the in-memory zone using an iterator of the underlying
174 : : // zone.
175 [ + - ][ + - ]: 38 : ConstElementPtr filetype = zone_config->get("filetype");
176 : 3 : const string filetype_txt = filetype ? filetype->stringValue() :
177 [ + + + - ]: 79 : "text";
[ + - ]
178 [ + - ]: 38 : boost::scoped_ptr<DataSourceClientContainer> container;
179 [ + - ][ + + ]: 38 : if (filetype_txt == "sqlite3") {
180 : : container.reset(new DataSourceClientContainer(
181 : : "sqlite3",
182 : : Element::fromJSON("{\"database_file\": \"" +
183 [ + - ][ + - ]: 8 : file_txt + "\"}")));
[ + - ][ + - ]
184 [ - + ]: 36 : } else if (filetype_txt != "text") {
185 [ # # ][ # # ]: 0 : isc_throw(AuthConfigError, "Invalid filetype for zone "
[ # # ][ # # ]
[ # # ][ # # ]
186 : : << origin_txt << ": " << filetype_txt);
187 : : }
188 : :
189 : : // Note: we don't want to have such small try-catch blocks for each
190 : : // specific error. We may eventually want to introduce some unified
191 : : // error handling framework as we have more configuration parameters.
192 : : // See bug #1627 for the relevant discussion.
193 : 38 : InMemoryZoneFinder* imzf = NULL;
194 : : try {
195 [ + + ][ + - ]: 38 : imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
[ + - ]
196 [ - + ]: 2 : } catch (const isc::dns::NameParserException& ex) {
197 [ - + ][ - + ]: 2 : isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
[ - + ][ - + ]
198 : : ex.what());
199 : : }
200 : :
201 : : boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
202 [ + - ]: 37 : const result::Result result = memory_client_->addZone(zone_finder);
203 [ + + ]: 37 : if (result == result::EXIST) {
204 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError, "zone "<< origin->str()
[ + - ][ + - ]
[ + - ][ + - ]
205 : : << " already exists");
206 : : }
207 : :
208 : : /*
209 : : * TODO: Once we have better reloading of configuration (something
210 : : * else than throwing everything away and loading it again), we will
211 : : * need the load method to be split into some kind of build and
212 : : * commit/abort parts.
213 : : */
214 [ + - ][ + + ]: 36 : if (filetype_txt == "text") {
215 [ + + ]: 34 : zone_finder->load(file_txt);
216 : : } else {
217 : : zone_finder->load(*container->getInstance().getIterator(
218 [ + - ][ + + ]: 4 : Name(origin_txt)));
[ + - ]
219 : : }
220 : : }
221 : : }
222 : :
223 : : /// A derived \c AuthConfigParser class for the "statistics-internal"
224 : : /// configuration identifier.
225 : 4 : class StatisticsIntervalConfig : public AuthConfigParser {
226 : : public:
227 : : StatisticsIntervalConfig(AuthSrv& server) :
228 : 8 : server_(server), interval_(0)
229 : : {}
230 : 8 : virtual void build(ConstElementPtr config_value) {
231 : 8 : const int32_t config_interval = config_value->intValue();
232 [ + + ]: 6 : if (config_interval < 0) {
233 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError, "Negative statistics interval value: "
[ + - ]
234 : : << config_interval);
235 : : }
236 [ + + ]: 5 : if (config_interval > 86400) {
237 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError, "Statistics interval value "
[ + - ][ + - ]
238 : : << config_interval
239 : : << " must be equal to or shorter than 86400");
240 : : }
241 : 4 : interval_ = config_interval;
242 : 4 : }
243 : 3 : virtual void commit() {
244 : : // setStatisticsTimerInterval() is not 100% exception free. But
245 : : // exceptions should happen only in a very rare situation, so we
246 : : // let them be thrown and subsequently regard them as a fatal error.
247 : 3 : server_.setStatisticsTimerInterval(interval_);
248 : 3 : }
249 : : private:
250 : : AuthSrv& server_;
251 : : uint32_t interval_;
252 : : };
253 : :
254 : : /// A special parser for testing: it throws from commit() despite the
255 : : /// suggested convention of the class interface.
256 : 1 : class ThrowerCommitConfig : public AuthConfigParser {
257 : : public:
258 : 1 : virtual void build(ConstElementPtr) {} // ignore param, do nothing
259 : 1 : virtual void commit() {
260 : 1 : throw 10;
261 : : }
262 : : };
263 : :
264 : : /**
265 : : * \brief Configuration parser for listen_on.
266 : : *
267 : : * It parses and sets the listening addresses of the server.
268 : : *
269 : : * It acts in unusual way. Since actually binding (changing) the sockets
270 : : * is an operation that is expected to throw often, it shouldn't happen
271 : : * in commit. Thefere we do it in build. But if the config is not committed
272 : : * then, we would have it wrong. So we store the old addresses and if
273 : : * commit is not called before destruction of the object, we return the
274 : : * old addresses (which is the same kind of dangerous operation, but it is
275 : : * expected that if we just managed to bind some and had the old ones binded
276 : : * before, it should work).
277 : : *
278 : : * We might do something better in future (like open only the ports that are
279 : : * extra, put them in in commit and close the old ones), but that's left out
280 : : * for now.
281 : : */
282 : : class ListenAddressConfig : public AuthConfigParser {
283 : : public:
284 : : ListenAddressConfig(AuthSrv& server) :
285 : 16 : server_(server)
286 : : { }
287 : 8 : ~ ListenAddressConfig() {
288 [ - + ]: 8 : if (rollbackAddresses_.get() != NULL) {
289 [ # # ]: 0 : server_.setListenAddresses(*rollbackAddresses_);
290 : : }
291 : 16 : }
292 : : private:
293 : : typedef auto_ptr<AddressList> AddrListPtr;
294 : : public:
295 : 8 : virtual void build(ConstElementPtr config) {
296 [ + + ]: 26 : AddressList newAddresses = parseAddresses(config, "listen_on");
297 [ + - ][ + - ]: 4 : AddrListPtr old(new AddressList(server_.getListenAddresses()));
[ + - ]
298 [ + + ]: 2 : server_.setListenAddresses(newAddresses);
299 : : /*
300 : : * Set the rollback addresses only after successful setting of the
301 : : * new addresses, so we don't try to rollback if the setup is
302 : : * unsuccessful (the above can easily throw).
303 : : */
304 : 1 : rollbackAddresses_ = old;
305 : 1 : }
306 : 1 : virtual void commit() {
307 : 1 : rollbackAddresses_.release();
308 : 1 : }
309 : : private:
310 : : AuthSrv& server_;
311 : : /**
312 : : * This is the old address list, if we expect to roll back. When we commit,
313 : : * this is set to NULL.
314 : : */
315 : : AddrListPtr rollbackAddresses_;
316 : : };
317 : :
318 : : // This is a generalized version of create function that can create
319 : : // an AuthConfigParser object for "internal" use.
320 : : AuthConfigParser*
321 : 99 : createAuthConfigParser(AuthSrv& server, const std::string& config_id,
322 : : bool internal)
323 : : {
324 : : // For the initial implementation we use a naive if-else blocks for
325 : : // simplicity. In future we'll probably generalize it using map-like
326 : : // data structure, and may even provide external register interface so
327 : : // that it can be dynamically customized.
328 [ + + ]: 99 : if (config_id == "datasources") {
329 : 39 : return (new DatasourcesConfig(server));
330 [ + + ]: 60 : } else if (config_id == "statistics-interval") {
331 : 4 : return (new StatisticsIntervalConfig(server));
332 [ + + ][ + + ]: 56 : } else if (internal && config_id == "datasources/memory") {
[ + + ]
333 : 43 : return (new MemoryDatasourceConfig(server));
334 [ + + ]: 13 : } else if (config_id == "listen_on") {
335 : 8 : return (new ListenAddressConfig(server));
336 [ + + ]: 5 : } else if (config_id == "_commit_throw") {
337 : : // This is for testing purpose only and should not appear in the
338 : : // actual configuration syntax. While this could crash the caller
339 : : // as a result, the server implementation is expected to perform
340 : : // syntax level validation and should be safe in practice. In future,
341 : : // we may introduce dynamic registration of configuration parsers,
342 : : // and then this test can be done in a cleaner and safer way.
343 : 1 : return (new ThrowerCommitConfig());
344 [ + + ]: 4 : } else if (config_id == "version") {
345 : : // Currently, the version identifier is ignored, but it should
346 : : // later be used to mark backwards incompatible changes in the
347 : : // config data
348 : 1 : return (new VersionConfig());
349 : : } else {
350 [ + - ][ + - ]: 102 : isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
[ + - ]
351 : : config_id);
352 : : }
353 : : }
354 : : } // end of unnamed namespace
355 : :
356 : : AuthConfigParser*
357 : 55 : createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
358 : 55 : return (createAuthConfigParser(server, config_id, false));
359 : : }
360 : :
361 : : void
362 : 39 : configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
363 [ + + ]: 39 : if (!config_set) {
364 [ + - ][ + - ]: 2 : isc_throw(AuthConfigError,
365 : : "Null pointer is passed to configuration parser");
366 : : }
367 : :
368 : : typedef boost::shared_ptr<AuthConfigParser> ParserPtr;
369 : 38 : vector<ParserPtr> parsers;
370 : : typedef pair<string, ConstElementPtr> ConfigPair;
371 : : try {
372 [ + + ][ + + ]: 143 : BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
[ + - ][ + - ]
[ + - ][ + + ]
[ + + ]
373 : : // We should eventually integrate the sqlite3 DB configuration to
374 : : // this framework, but to minimize diff we begin with skipping that
375 : : // part.
376 [ + - ][ + + ]: 38 : if (config_pair.first == "database_file") {
377 : 5 : continue;
378 : : }
379 : :
380 : : ParserPtr parser(createAuthConfigParser(server,
381 [ + + ][ + - ]: 33 : config_pair.first));
382 [ + + ]: 64 : parser->build(config_pair.second);
383 [ + - ]: 24 : parsers.push_back(parser);
384 : : }
385 : 2 : } catch (const AuthConfigError& ex) {
386 : 1 : throw; // simply rethrowing it
387 [ - + + ]: 19 : } catch (const isc::Exception& ex) {
388 [ - + ][ - + ]: 18 : isc_throw(AuthConfigError, "Server configuration failed: " <<
[ - + ][ - + ]
389 : : ex.what());
390 : : }
391 : :
392 : : try {
393 [ + + ][ + - ]: 117 : BOOST_FOREACH(ParserPtr parser, parsers) {
[ + - ][ + + ]
[ + + ]
394 [ + + ]: 23 : parser->commit();
395 : : }
396 : 2 : } catch (...) {
397 : : throw FatalError("Unrecoverable error: "
398 [ - + ]: 2 : "a configuration parser threw in commit");
399 : : }
400 : 28 : }
|