auth_config.cc 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <set>
#include <string>
#include <utility>
#include <vector>

#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>

#include <dns/name.h>
#include <dns/rrclass.h>

#include <cc/data.h>

#include <datasrc/memory_datasrc.h>
#include <datasrc/zonetable.h>

#include <auth/auth_srv.h>
32
#include <auth/auth_config.h>
33
#include <auth/common.h>
34

35 36
#include <server_common/portconfig.h>

37 38 39 40 41
using namespace std;
using boost::shared_ptr;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
42
using namespace isc::server_common::portconfig;
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

namespace {
// Forward declaration
AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id,
                       bool internal);

/// A derived \c AuthConfigParser class for the "datasources" configuration
/// identifier.
class DatasourcesConfig : public AuthConfigParser {
public:
    DatasourcesConfig(AuthSrv& server) : server_(server) {}
    virtual void build(ConstElementPtr config_value);
    virtual void commit();
private:
    AuthSrv& server_;
    vector<shared_ptr<AuthConfigParser> > datasources_;
    set<string> configured_sources_;
};

63 64 65 66 67 68 69 70 71
/// A derived \c AuthConfigParser for the version value
/// (which is not used at this moment)
class VersionConfig : public AuthConfigParser {
public:
    VersionConfig() {}
    virtual void build(ConstElementPtr) {};
    virtual void commit() {};
};

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
void
DatasourcesConfig::build(ConstElementPtr config_value) {
    BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
        // The caller is supposed to perform syntax-level checks, but we'll
        // do minimum level of validation ourselves so that we won't crash due
        // to a buggy application.
        ConstElementPtr datasrc_type = datasrc_elem->get("type");
        if (!datasrc_type) {
            isc_throw(AuthConfigError, "Missing data source type");
        }

        if (configured_sources_.find(datasrc_type->stringValue()) !=
            configured_sources_.end()) {
            isc_throw(AuthConfigError, "Data source type '" <<
                      datasrc_type->stringValue() << "' already configured");
        }
        
        shared_ptr<AuthConfigParser> datasrc_config =
            shared_ptr<AuthConfigParser>(
                createAuthConfigParser(server_, string("datasources/") +
                                       datasrc_type->stringValue(),
                                       true));
        datasrc_config->build(datasrc_elem);
        datasources_.push_back(datasrc_config);

        configured_sources_.insert(datasrc_type->stringValue());
    }
}

void
DatasourcesConfig::commit() {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
103 104 105 106 107 108 109
    // XXX a short term workaround: clear all data sources and then reset
    // to new ones so that we can remove data sources that don't exist in
    // the new configuration and have been used in the server.
    // This could be inefficient and requires knowledge about
    // server implementation details, and isn't scalable wrt the number of
    // data source types, and should eventually be improved.
    // Currently memory data source for class IN is the only possibility.
110
    server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
JINMEI Tatuya's avatar
JINMEI Tatuya committed
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
    BOOST_FOREACH(shared_ptr<AuthConfigParser> datasrc_config, datasources_) {
        datasrc_config->commit();
    }
}

/// A derived \c AuthConfigParser class for the memory type datasource
/// configuration.  It does not correspond to the configuration syntax;
/// it's instantiated for internal use.
class MemoryDatasourceConfig : public AuthConfigParser {
public:
    MemoryDatasourceConfig(AuthSrv& server) :
        server_(server),
        rrclass_(0)              // XXX: dummy initial value
    {}
    virtual void build(ConstElementPtr config_value);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
127
    virtual void commit() {
128
        server_.setInMemoryClient(rrclass_, memory_client_);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
129
    }
130 131 132
private:
    AuthSrv& server_;
    RRClass rrclass_;
133
    AuthSrv::InMemoryClientPtr memory_client_;
134 135 136 137 138 139 140 141 142 143 144 145
};

void
MemoryDatasourceConfig::build(ConstElementPtr config_value) {
    // XXX: apparently we cannot retrieve the default RR class from the
    // module spec.  As a temporary workaround we hardcode the default value.
    ConstElementPtr rrclass_elem = config_value->get("class");
    rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");

    // We'd eventually optimize building zones (in case of reloading) by
    // selectively loading fresh zones.  Right now we simply check the
    // RR class is supported by the server implementation.
146 147
    server_.getInMemoryClient(rrclass_);
    memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

    ConstElementPtr zones_config = config_value->get("zones");
    if (!zones_config) {
        // XXX: Like the RR class, we cannot retrieve the default value here,
        // so we assume an empty zone list in this case.
        return;
    }

    BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
        ConstElementPtr origin = zone_config->get("origin");
        if (!origin) {
            isc_throw(AuthConfigError, "Missing zone origin");
        }
        ConstElementPtr file = zone_config->get("file");
        if (!file) {
            isc_throw(AuthConfigError, "Missing zone file for zone: "
                      << origin->str());
        }
166 167
        shared_ptr<InMemoryZoneFinder> zone_finder(new
                                                   InMemoryZoneFinder(rrclass_,
Michal Vaner's avatar
Michal Vaner committed
168
            Name(origin->stringValue())));
169
        const result::Result result = memory_client_->addZone(zone_finder);
170 171 172 173 174
        if (result == result::EXIST) {
            isc_throw(AuthConfigError, "zone "<< origin->str()
                      << " already exists");
        }

Michal Vaner's avatar
Michal Vaner committed
175 176 177 178 179 180
        /*
         * TODO: Once we have better reloading of configuration (something
         * else than throwing everything away and loading it again), we will
         * need the load method to be split into some kind of build and
         * commit/abort parts.
         */
181
        zone_finder->load(file->stringValue());
182 183 184
    }
}

185 186
/// A derived \c AuthConfigParser class for the "statistics-internal"
/// configuration identifier.
187 188 189
class StatisticsIntervalConfig : public AuthConfigParser {
public:
    StatisticsIntervalConfig(AuthSrv& server) :
190
        server_(server), interval_(0)
191
    {}
192
    virtual void build(ConstElementPtr config_value) {
193 194
        const int32_t config_interval = config_value->intValue();
        if (config_interval < 0) {
195
            isc_throw(AuthConfigError, "Negative statistics interval value: "
196 197
                      << config_interval);
        }
198
        if (config_interval > 86400) {
199 200 201
            isc_throw(AuthConfigError, "Statistics interval value "
                      << config_interval
                      << " must be equal to or shorter than 86400");
202
        }
203
        interval_ = config_interval;
204 205 206 207 208 209 210
    }
    virtual void commit() {
        // setStatisticsTimerInterval() is not 100% exception free.  But
        // exceptions should happen only in a very rare situation, so we
        // let them be thrown and subsequently regard them as a fatal error.
        server_.setStatisticsTimerInterval(interval_);
    }
211 212
private:
    AuthSrv& server_;
213
    uint32_t interval_;
214 215
};

216 217 218 219 220 221 222 223 224 225
/// A special parser for testing: it throws from commit() despite the
/// suggested convention of the class interface.
class ThrowerCommitConfig : public AuthConfigParser {
public:
    virtual void build(ConstElementPtr) {} // ignore param, do nothing
    virtual void commit() {
        throw 10;
    }
};

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
/**
 * \brief Configuration parser for listen_on.
 *
 * It parses and sets the listening addresses of the server.
 *
 * It acts in unusual way. Since actually binding (changing) the sockets
 * is an operation that is expected to throw often, it shouldn't happen
 * in commit. Thefere we do it in build. But if the config is not committed
 * then, we would have it wrong. So we store the old addresses and if
 * commit is not called before destruction of the object, we return the
 * old addresses (which is the same kind of dangerous operation, but it is
 * expected that if we just managed to bind some and had the old ones binded
 * before, it should work).
 *
 * We might do something better in future (like open only the ports that are
 * extra, put them in in commit and close the old ones), but that's left out
 * for now.
 */
class ListenAddressConfig : public AuthConfigParser {
public:
    ListenAddressConfig(AuthSrv& server) :
        server_(server)
    { }
    ~ ListenAddressConfig() {
        if (rollbackAddresses_.get() != NULL) {
            server_.setListenAddresses(*rollbackAddresses_);
        }
    }
private:
    typedef auto_ptr<AddressList> AddrListPtr;
public:
    virtual void build(ConstElementPtr config) {
        AddressList newAddresses = parseAddresses(config, "listen_on");
        AddrListPtr old(new AddressList(server_.getListenAddresses()));
        server_.setListenAddresses(newAddresses);
        /*
         * Set the rollback addresses only after successful setting of the
         * new addresses, so we don't try to rollback if the setup is
         * unsuccessful (the above can easily throw).
         */
        rollbackAddresses_ = old;
    }
    virtual void commit() {
        rollbackAddresses_.release();
    }
private:
    AuthSrv& server_;
    /**
     * This is the old address list, if we expect to roll back. When we commit,
     * this is set to NULL.
     */
    AddrListPtr rollbackAddresses_;
};

280
// This is a generalized version of create function that can create
JINMEI Tatuya's avatar
JINMEI Tatuya committed
281
// an AuthConfigParser object for "internal" use.
282 283 284 285 286 287 288 289 290 291
AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id,
                       bool internal)
{
    // For the initial implementation we use a naive if-else blocks for
    // simplicity.  In future we'll probably generalize it using map-like
    // data structure, and may even provide external register interface so
    // that it can be dynamically customized.
    if (config_id == "datasources") {
        return (new DatasourcesConfig(server));
292 293
    } else if (config_id == "statistics-interval") {
        return (new StatisticsIntervalConfig(server));
294 295
    } else if (internal && config_id == "datasources/memory") {
        return (new MemoryDatasourceConfig(server));
296 297
    } else if (config_id == "listen_on") {
        return (new ListenAddressConfig(server));
298 299 300 301 302 303 304 305
    } else if (config_id == "_commit_throw") {
        // This is for testing purpose only and should not appear in the
        // actual configuration syntax.  While this could crash the caller
        // as a result, the server implementation is expected to perform
        // syntax level validation and should be safe in practice.  In future,
        // we may introduce dynamic registration of configuration parsers,
        // and then this test can be done in a cleaner and safer way.
        return (new ThrowerCommitConfig());
306 307 308 309 310
    } else if (config_id == "version") {
        // Currently, the version identifier is ignored, but it should
        // later be used to mark backwards incompatible changes in the
        // config data
        return (new VersionConfig());
311
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
312
        isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
                  config_id);
    }
}
} // end of unnamed namespace

AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
    return (createAuthConfigParser(server, config_id, false));
}

void
configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
    if (!config_set) {
        isc_throw(AuthConfigError,
                  "Null pointer is passed to configuration parser");
    }

    typedef shared_ptr<AuthConfigParser> ParserPtr;
    vector<ParserPtr> parsers;
    typedef pair<string, ConstElementPtr> ConfigPair;
    try {
        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
            // We should eventually integrate the sqlite3 DB configuration to
            // this framework, but to minimize diff we begin with skipping that
            // part.
            if (config_pair.first == "database_file") {
                continue;
            }

            ParserPtr parser(createAuthConfigParser(server,
                                                    config_pair.first));
            parser->build(config_pair.second);
            parsers.push_back(parser);
        }
    } catch (const AuthConfigError& ex) {
348
        throw;                  // simply rethrowing it
349 350 351 352 353
    } catch (const isc::Exception& ex) {
        isc_throw(AuthConfigError, "Server configuration failed: " <<
                  ex.what());
    }

354 355 356 357 358 359 360
    try {
        BOOST_FOREACH(ParserPtr parser, parsers) {
            parser->commit();
        }
    } catch (...) {
        throw FatalError("Unrecoverable error: "
                         "a configuration parser threw in commit");
361 362
    }
}