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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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
348
349
350
351
352
353
// Copyright (C) 2017-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_service_mgr.h>
#include <d2srv/d2_config.h>
#include <d2srv/d2_simple_parser.h>
#include <cc/data.h>
#include <hooks/hooks_manager.h>
#include <hooks/hooks_parser.h>

using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::d2;
using namespace isc;

namespace {

dhcp_ddns::NameChangeProtocol
getProtocol(ConstElementPtr map, const std::string& name) {
    ConstElementPtr value = map->get(name);
    if (!value) {
        isc_throw(D2CfgError, "Mandatory parameter " << name
                  << " not found (" << map->getPosition() << ")");
    }
    std::string str = value->stringValue();
    try {
        return (dhcp_ddns::stringToNcrProtocol(str));
    } catch (const std::exception& ex) {
        isc_throw(D2CfgError,
                  "invalid NameChangeRequest protocol (" << str
                  << ") specified for parameter '" << name
                  << "' (" << value->getPosition() << ")");
    }
}

dhcp_ddns::NameChangeFormat
getFormat(ConstElementPtr map, const std::string& name) {
    ConstElementPtr value = map->get(name);
    if (!value) {
        isc_throw(D2CfgError, "Mandatory parameter " << name
                  << " not found (" << map->getPosition() << ")");
    }
    std::string str = value->stringValue();
    try {
        return (dhcp_ddns::stringToNcrFormat(str));
    } catch (const std::exception& ex) {
        isc_throw(D2CfgError,
                  "invalid NameChangeRequest format (" << str
                  << ") specified for parameter '" << name
                  << "' (" << value->getPosition() << ")");
    }
}

} // anon

namespace isc {
namespace d2 {
/// @brief This sets of arrays define the default values and
///        values inherited (derived) between various scopes.
///
/// Each of those is documented in @file d2_simple_parser.cc. This
/// is different than most other comments in Kea code. The reason
/// for placing those in .cc rather than .h file is that it
/// is expected to be one centralized place to look at for
/// the default values. This is expected to be looked at also by
/// people who are not skilled in C or C++, so they may be
/// confused with the differences between declaration and definition.
/// As such, there's one file to look at that hopefully is readable
/// without any C or C++ skills.
///
/// @{

/// @brief This table defines default global values for D2
///
/// Some of the global parameters defined in the global scope (i.e. directly
/// in DhcpDdns) are optional. If not defined, the following values will be
/// used.
const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
    { "ip-address",         Element::string, "127.0.0.1" },
    { "port",               Element::integer, "53001" },
    { "dns-server-timeout", Element::integer, "500" }, // in milliseconds
    { "ncr-protocol",       Element::string, "UDP" },
    { "ncr-format",         Element::string, "JSON" }
};

/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
const SimpleDefaults D2SimpleParser::TSIG_KEY_DEFAULTS = {
    { "digest-bits", Element::integer, "0" }
};

/// Supplies defaults for optional values in DDNS domain managers
/// (e.g. "forward-ddns" and "reverse-ddns").
/// @note  While there are none yet defined, it is highly likely
/// there will be domain manager defaults added in the future.
/// This code to set defaults already uses this list, so supporting
/// values will simply require adding them to this list.
const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_MGR_DEFAULTS = {
};

/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_DEFAULTS = {
    { "key-name", Element::string, "" }
};

/// Supplies defaults for optional values DdnsDomain entries.
const SimpleDefaults D2SimpleParser::DNS_SERVER_DEFAULTS = {
    { "hostname", Element::string, "" },
    { "port",     Element::integer, "53" },
    { "key-name", Element::string, "" }
};

/// @}

/// ---------------------------------------------------------------------------
/// --- end of default values -------------------------------------------------
/// ---------------------------------------------------------------------------

size_t
D2SimpleParser::setAllDefaults(isc::data::ElementPtr global) {
    size_t cnt = 0;
    // Set global defaults first.
    cnt = setDefaults(global, D2_GLOBAL_DEFAULTS);

    // If the key list is present, set its members' defaults
    if (global->find("tsig-keys")) {
        ConstElementPtr keys = global->get("tsig-keys");
        cnt += setListDefaults(keys, TSIG_KEY_DEFAULTS);
    } else {
        // Not present, so add an empty list.
        ConstElementPtr list(new ListElement());
        global->set("tsig-keys", list);
        cnt++;
    }

    // Set the forward domain manager defaults.
    cnt += setManagerDefaults(global, "forward-ddns", DDNS_DOMAIN_MGR_DEFAULTS);

    // Set the reverse domain manager defaults.
    cnt += setManagerDefaults(global, "reverse-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
    return (cnt);
}

size_t
D2SimpleParser::setDdnsDomainDefaults(ElementPtr domain,
                                      const SimpleDefaults& domain_defaults) {
    size_t cnt = 0;

    // Set the domain's scalar defaults
    cnt += setDefaults(domain, domain_defaults);
    if (domain->find("dns-servers")) {
        // Now add the defaults to its server list.
        ConstElementPtr servers = domain->get("dns-servers");
        cnt += setListDefaults(servers, DNS_SERVER_DEFAULTS);
    }

    return (cnt);
}


size_t
D2SimpleParser::setManagerDefaults(ElementPtr global,
                                   const std::string& mgr_name,
                                   const SimpleDefaults& mgr_defaults) {
    size_t cnt = 0;

    if (!global->find(mgr_name)) {
        // If it's not present, then default is an empty map
        ConstElementPtr map(new MapElement());
        global->set(mgr_name, map);
        ++cnt;
    } else {
        // Get a writable copy of the manager element map
        ElementPtr mgr =
            boost::const_pointer_cast<Element>(global->get(mgr_name));

        // Set the manager's scalar defaults first
        cnt += setDefaults(mgr, mgr_defaults);

        // Get the domain list and set defaults for them.
        // The domain list may not be present ddns for this
        // manager is disabled.
        if (mgr->find("ddns-domains")) {
            ConstElementPtr domains = mgr->get("ddns-domains");
            for (auto const& domain : domains->listValue()) {
                // Set the domain's defaults.  We can't use setListDefaults()
                // as this does not handle sub-lists or maps, like server list.
                cnt += setDdnsDomainDefaults(domain, DDNS_DOMAIN_DEFAULTS);<--- Consider using std::accumulate algorithm instead of a raw loop.
            }
        }

    }

    return (cnt);
}

void D2SimpleParser::parse(const D2CfgContextPtr& ctx,
                           const isc::data::ConstElementPtr& config,
                           bool check_only) {
    // TSIG keys need to parse before the Domains, so we can catch Domains
    // that specify undefined keys. Create the necessary parsing order now.
    // addToParseOrder("tsig-keys");
    // addToParseOrder("forward-ddns");
    // addToParseOrder("reverse-ddns");

    ConstElementPtr keys = config->get("tsig-keys");
    if (keys) {
        TSIGKeyInfoListParser parser;
        ctx->setKeys(parser.parse(keys));
    }

    ConstElementPtr fwd = config->get("forward-ddns");
    if (fwd) {
        DdnsDomainListMgrParser parser;
        DdnsDomainListMgrPtr mgr = parser.parse(fwd, "forward-ddns",
                                                ctx->getKeys());
        ctx->setForwardMgr(mgr);
    }

    ConstElementPtr rev = config->get("reverse-ddns");
    if (rev) {
        DdnsDomainListMgrParser parser;
        DdnsDomainListMgrPtr mgr = parser.parse(rev, "reverse-ddns",
                                                ctx->getKeys());
        ctx->setReverseMgr(mgr);
    }

    // Fetch the parameters in the config, performing any logical
    // validation required.
    asiolink::IOAddress ip_address(0);
    uint32_t port = 0;
    uint32_t dns_server_timeout = 0;
    dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
    dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;

    ip_address = SimpleParser::getAddress(config, "ip-address");
    port = SimpleParser::getUint32(config, "port");
    dns_server_timeout = SimpleParser::getUint32(config, "dns-server-timeout");

    ncr_protocol = getProtocol(config, "ncr-protocol");
    if (ncr_protocol != dhcp_ddns::NCR_UDP) {
        isc_throw(D2CfgError, "ncr-protocol : "
                  << dhcp_ddns::ncrProtocolToString(ncr_protocol)
                  << " is not yet supported ("
                  << config->get("ncr-protocol")->getPosition() << ")");
    }

    ncr_format = getFormat(config, "ncr-format");
    if (ncr_format != dhcp_ddns::FMT_JSON) {
        isc_throw(D2CfgError, "NCR Format:"
                  << dhcp_ddns::ncrFormatToString(ncr_format)
                  << " is not yet supported"
                  << " (" << config->get("ncr-format")->getPosition() << ")");
    }

    ConstElementPtr user = config->get("user-context");
    if (user) {
        ctx->setContext(user);
    }

    // Get control sockets.
    ConstElementPtr control_sockets = config->get("control-sockets");
    ConstElementPtr control_socket = config->get("control-socket");
    if (control_socket) {
        ElementPtr l = Element::createList();
        l->add(UserContext::toElement(control_socket));
        control_sockets = l;
    }
    if (control_sockets) {
        if (control_sockets->getType() != Element::list) {
            // Sanity check: not supposed to fail.
            isc_throw(D2CfgError,
                      "Specified control-sockets is expected to be a list");
        }
        bool seen_unix(false);
        bool seen_http(false);
        for (ConstElementPtr socket : control_sockets->listValue()) {
            if (socket->getType() != Element::map) {
                // Sanity check: not supposed to fail.
                isc_throw(D2CfgError,
                          "Specified control-sockets is expected to be a list of maps");
            }
            ConstElementPtr socket_type = socket->get("socket-type");
            if (!socket_type) {
                isc_throw(D2CfgError,
                          "'socket-type' parameter is mandatory in control-sockets items");
            }
            if (socket_type->getType() != Element::string) {
                // Sanity check: not supposed to fail.
                isc_throw(D2CfgError,
                          "'socket-type' parameter is expected to be a string");
            }
            std::string type = socket_type->stringValue();
            if (type == "unix") {
                if (seen_unix) {
                    isc_throw(D2CfgError,
                              "control socket of type 'unix' already configured");
                }
                seen_unix = true;
                ctx->setUnixControlSocketInfo(socket);
            } else if ((type == "http") || (type == "https")) {
                if (seen_http) {
                    isc_throw(D2CfgError,
                              "control socket of type 'http' or 'https'"
                              " already configured");
                }
                seen_http = true;
                using namespace isc::config;
                HttpCommandConfigPtr http_config(new HttpCommandConfig(socket));
                ctx->setHttpControlSocketInfo(http_config);
            } else {
                // Sanity check: not supposed to fail.
                isc_throw(D2CfgError,
                          "unsupported 'socket-type': '" << type
                          << "' not 'unix', 'http' or 'https'");
            }
        }
    }

    // Finally, let's get the hook libs!
    using namespace isc::hooks;
    HooksConfig& libraries = ctx->getHooksConfig();
    ConstElementPtr hooks = config->get("hooks-libraries");
    if (hooks) {
        HooksLibrariesParser hooks_parser;
        hooks_parser.parse(libraries, hooks);
        libraries.verifyLibraries(hooks->getPosition(), false);
    }

    // Attempt to create the new client config. This ought to fly as
    // we already validated everything.
    D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout,
                                    ncr_protocol, ncr_format));

    ctx->getD2Params() = params;

    if (!check_only) {
        // This occurs last as if it succeeds, there is no easy way
        // revert it.  As a result, the failure to commit a subsequent
        // change causes problems when trying to roll back.
        HooksManager::prepareUnloadLibraries();
        static_cast<void>(HooksManager::unloadLibraries());
        IOServiceMgr::instance().clearIOServices();
        libraries.loadLibraries(false);
    }
}

}
}