auth_config.cc 14 KB

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