1 // Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <cc/command_interpreter.h>
10 #include <database/dbaccess_parser.h>
11 #include <database/backend_selector.h>
12 #include <database/server_selector.h>
13 #include <dhcp4/dhcp4_log.h>
14 #include <dhcp4/dhcp4_srv.h>
15 #include <dhcp4/json_config_parser.h>
16 #include <dhcp/libdhcp++.h>
17 #include <dhcp/option_definition.h>
18 #include <dhcpsrv/cb_ctl_dhcp4.h>
19 #include <dhcpsrv/cfg_option.h>
20 #include <dhcpsrv/cfgmgr.h>
21 #include <dhcpsrv/config_backend_dhcp4_mgr.h>
22 #include <dhcpsrv/db_type.h>
23 #include <dhcpsrv/parsers/client_class_def_parser.h>
24 #include <dhcpsrv/parsers/dhcp_parsers.h>
25 #include <dhcpsrv/parsers/expiration_config_parser.h>
26 #include <dhcpsrv/parsers/host_reservation_parser.h>
27 #include <dhcpsrv/parsers/host_reservations_list_parser.h>
28 #include <dhcpsrv/parsers/ifaces_config_parser.h>
29 #include <dhcpsrv/parsers/multi_threading_config_parser.h>
30 #include <dhcpsrv/parsers/option_data_parser.h>
31 #include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
32 #include <dhcpsrv/parsers/simple_parser4.h>
33 #include <dhcpsrv/parsers/shared_networks_list_parser.h>
34 #include <dhcpsrv/parsers/sanity_checks_parser.h>
35 #include <dhcpsrv/host_data_source_factory.h>
36 #include <dhcpsrv/timer_mgr.h>
37 #include <process/config_ctl_parser.h>
38 #include <hooks/hooks_manager.h>
39 #include <hooks/hooks_parser.h>
40 #include <config/command_mgr.h>
41 #include <util/encode/hex.h>
42 #include <util/strutil.h>
43 
44 #include <boost/foreach.hpp>
45 #include <boost/lexical_cast.hpp>
46 #include <boost/algorithm/string.hpp>
47 
48 #include <limits>
49 #include <iostream>
50 #include <iomanip>
51 #include <netinet/in.h>
52 #include <vector>
53 #include <map>
54 
55 using namespace std;
56 using namespace isc;
57 using namespace isc::dhcp;
58 using namespace isc::data;
59 using namespace isc::asiolink;
60 using namespace isc::hooks;
61 using namespace isc::process;
62 using namespace isc::config;
63 
64 namespace {
65 
66 /// @brief Parser that takes care of global DHCPv4 parameters and utility
67 ///        functions that work on global level.
68 ///
69 /// This class is a collection of utility method that either handle
70 /// global parameters (see @ref parse), or conducts operations on
71 /// global level (see @ref sanityChecks and @ref copySubnets4).
72 ///
73 /// See @ref parse method for a list of supported parameters.
74 class Dhcp4ConfigParser : public isc::data::SimpleParser {
75 public:
76 
77     /// @brief Sets global parameters in staging configuration
78     ///
79     /// @param global global configuration scope
80     /// @param cfg Server configuration (parsed parameters will be stored here)
81     ///
82     /// Currently this method sets the following global parameters:
83     ///
84     /// - echo-client-id
85     /// - decline-probation-period
86     /// - dhcp4o6-port
87     /// - user-context
88     ///
89     /// @throw DhcpConfigError if parameters are missing or
90     /// or having incorrect values.
parse(const SrvConfigPtr & cfg,const ConstElementPtr & global)91     void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
92 
93         // Set whether v4 server is supposed to echo back client-id
94         // (yes = RFC6842 compatible, no = backward compatibility)
95         bool echo_client_id = getBoolean(global, "echo-client-id");
96         cfg->setEchoClientId(echo_client_id);
97 
98         // Set the probation period for decline handling.
99         uint32_t probation_period =
100             getUint32(global, "decline-probation-period");
101         cfg->setDeclinePeriod(probation_period);
102 
103         // Set the DHCPv4-over-DHCPv6 interserver port.
104         uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
105         cfg->setDhcp4o6Port(dhcp4o6_port);
106 
107         // Set the global user context.
108         ConstElementPtr user_context = global->get("user-context");
109         if (user_context) {
110             cfg->setContext(user_context);
111         }
112 
113         // Set the server's logical name
114         std::string server_tag = getString(global, "server-tag");
115         cfg->setServerTag(server_tag);
116     }
117 
118     /// @brief Sets global parameters before other parameters are parsed.
119     ///
120     /// This method sets selected global parameters before other parameters
121     /// are parsed. This is important when the behavior of the parsers
122     /// run later depends on these global parameters.
123     ///
124     /// Currently this method sets the following global parameters:
125     /// - ip-reservations-unique
126     ///
127     /// @param global global configuration scope
128     /// @param cfg Server configuration (parsed parameters will be stored here)
parseEarly(const SrvConfigPtr & cfg,const ConstElementPtr & global)129     void parseEarly(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
130         // Set ip-reservations-unique flag.
131         bool ip_reservations_unique = getBoolean(global, "ip-reservations-unique");
132         cfg->setIPReservationsUnique(ip_reservations_unique);
133     }
134 
135     /// @brief Copies subnets from shared networks to regular subnets container
136     ///
137     /// @param from pointer to shared networks container (copy from here)
138     /// @param dest pointer to cfg subnets4 (copy to here)
139     /// @throw BadValue if any pointer is missing
140     /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
141     void
copySubnets4(const CfgSubnets4Ptr & dest,const CfgSharedNetworks4Ptr & from)142     copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
143 
144         if (!dest || !from) {
145             isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
146         }
147 
148         const SharedNetwork4Collection* networks = from->getAll();
149         if (!networks) {
150             // Nothing to copy. Technically, it should return a pointer to empty
151             // container, but let's handle null pointer as well.
152             return;
153         }
154 
155         // Let's go through all the networks one by one
156         for (auto net = networks->begin(); net != networks->end(); ++net) {
157 
158             // For each network go through all the subnets in it.
159             const Subnet4Collection* subnets = (*net)->getAllSubnets();
160             if (!subnets) {
161                 // Shared network without subnets it weird, but we decided to
162                 // accept such configurations.
163                 continue;
164             }
165 
166             // For each subnet, add it to a list of regular subnets.
167             for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
168                 dest->add(*subnet);
169             }
170         }
171     }
172 
173     /// @brief Conducts global sanity checks
174     ///
175     /// This method is very simple now, but more sanity checks are expected
176     /// in the future.
177     ///
178     /// @param cfg - the parsed structure
179     /// @param global global Dhcp4 scope
180     /// @throw DhcpConfigError in case of issues found
181     void
sanityChecks(const SrvConfigPtr & cfg,const ConstElementPtr & global)182     sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
183 
184         /// Global lifetime sanity checks
185         cfg->sanityChecksLifetime("valid-lifetime");
186 
187         /// Shared network sanity checks
188         const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
189         if (networks) {
190             sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
191         }
192     }
193 
194     /// @brief Sanity checks for shared networks
195     ///
196     /// This method verifies if there are no issues with shared networks.
197     /// @param networks pointer to shared networks being checked
198     /// @param json shared-networks element
199     /// @throw DhcpConfigError if issues are encountered
200     void
sharedNetworksSanityChecks(const SharedNetwork4Collection & networks,ConstElementPtr json)201     sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
202                                ConstElementPtr json) {
203 
204         /// @todo: in case of errors, use json to extract line numbers.
205         if (!json) {
206             // No json? That means that the shared-networks was never specified
207             // in the config.
208             return;
209         }
210 
211         // Used for names uniqueness checks.
212         std::set<string> names;
213 
214         // Let's go through all the networks one by one
215         for (auto net = networks.begin(); net != networks.end(); ++net) {
216             string txt;
217 
218             // Let's check if all subnets have either the same interface
219             // or don't have the interface specified at all.
220             bool authoritative = (*net)->getAuthoritative();
221             string iface = (*net)->getIface();
222 
223             const Subnet4Collection* subnets = (*net)->getAllSubnets();
224             if (subnets) {
225                 // For each subnet, add it to a list of regular subnets.
226                 for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
227                     if ((*subnet)->getAuthoritative() != authoritative) {
228                         isc_throw(DhcpConfigError, "Subnet " << boolalpha
229                                   << (*subnet)->toText()
230                                   << " has different authoritative setting "
231                                   << (*subnet)->getAuthoritative()
232                                   << " than the shared-network itself: "
233                                   << authoritative);
234                     }
235 
236                     if (iface.empty()) {
237                         iface = (*subnet)->getIface();
238                         continue;
239                     }
240 
241                     if ((*subnet)->getIface().empty()) {
242                         continue;
243                     }
244 
245                     if ((*subnet)->getIface() != iface) {
246                         isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
247                                   << " has specified interface " << (*subnet)->getIface()
248                                   << ", but earlier subnet in the same shared-network"
249                                   << " or the shared-network itself used " << iface);
250                     }
251 
252                     // Let's collect the subnets in case we later find out the
253                     // subnet doesn't have a mandatory name.
254                     txt += (*subnet)->toText() + " ";
255                 }
256             }
257 
258             // Next, let's check name of the shared network.
259             if ((*net)->getName().empty()) {
260                 isc_throw(DhcpConfigError, "Shared-network with subnets "
261                           << txt << " is missing mandatory 'name' parameter");
262             }
263 
264             // Is it unique?
265             if (names.find((*net)->getName()) != names.end()) {
266                 isc_throw(DhcpConfigError, "A shared-network with "
267                           "name " << (*net)->getName() << " defined twice.");
268             }
269             names.insert((*net)->getName());
270 
271         }
272     }
273 };
274 
275 } // anonymous namespace
276 
277 namespace isc {
278 namespace dhcp {
279 
280 /// @brief Initialize the command channel based on the staging configuration
281 ///
282 /// Only close the current channel, if the new channel configuration is
283 /// different.  This avoids disconnecting a client and hence not sending them
284 /// a command result, unless they specifically alter the channel configuration.
285 /// In that case the user simply has to accept they'll be disconnected.
286 ///
configureCommandChannel()287 void configureCommandChannel() {
288     // Get new socket configuration.
289     ConstElementPtr sock_cfg =
290         CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
291 
292     // Get current socket configuration.
293     ConstElementPtr current_sock_cfg =
294             CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
295 
296     // Determine if the socket configuration has changed. It has if
297     // both old and new configuration is specified but respective
298     // data elements aren't equal.
299     bool sock_changed = (sock_cfg && current_sock_cfg &&
300                          !sock_cfg->equals(*current_sock_cfg));
301 
302     // If the previous or new socket configuration doesn't exist or
303     // the new configuration differs from the old configuration we
304     // close the existing socket and open a new socket as appropriate.
305     // Note that closing an existing socket means the client will not
306     // receive the configuration result.
307     if (!sock_cfg || !current_sock_cfg || sock_changed) {
308         // Close the existing socket (if any).
309         isc::config::CommandMgr::instance().closeCommandSocket();
310 
311         if (sock_cfg) {
312             // This will create a control socket and install the external
313             // socket in IfaceMgr. That socket will be monitored when
314             // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
315             // callback in CommandMgr will be called, if necessary.
316             isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
317         }
318     }
319 }
320 
321 isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv & server,isc::data::ConstElementPtr config_set,bool check_only)322 configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
323                      bool check_only) {
324     if (!config_set) {
325         ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
326                                  string("Can't parse NULL config"));
327         return (answer);
328     }
329 
330     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START)
331         .arg(server.redactConfig(config_set)->str());
332 
333     // Before starting any subnet operations, let's reset the subnet-id counter,
334     // so newly recreated configuration starts with first subnet-id equal 1.
335     Subnet::resetSubnetID();
336 
337     // Close DHCP sockets and remove any existing timers.
338     if (!check_only) {
339         IfaceMgr::instance().closeSockets();
340         TimerMgr::instance()->unregisterTimers();
341         server.discardPackets();
342         server.getCBControl()->reset();
343     }
344 
345     // Revert any runtime option definitions configured so far and not committed.
346     LibDHCP::revertRuntimeOptionDefs();
347     // Let's set empty container in case a user hasn't specified any configuration
348     // for option definitions. This is equivalent to committing empty container.
349     LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
350 
351     // Print the list of known backends.
352     HostDataSourceFactory::printRegistered();
353 
354     // Answer will hold the result.
355     ConstElementPtr answer;
356     // Rollback informs whether error occurred and original data
357     // have to be restored to global storages.
358     bool rollback = false;
359     // Global parameter name in case of an error.
360     string parameter_name;
361     ElementPtr mutable_cfg;
362     SrvConfigPtr srv_cfg;
363     try {
364         // Get the staging configuration
365         srv_cfg = CfgMgr::instance().getStagingCfg();
366 
367         // This is a way to convert ConstElementPtr to ElementPtr.
368         // We need a config that can be edited, because we will insert
369         // default values and will insert derived values as well.
370         mutable_cfg = boost::const_pointer_cast<Element>(config_set);
371 
372         // Relocate dhcp-ddns parameters that have moved to global scope.
373         // Rule is that a global value overrides the dhcp-ddns value, so
374         // we need to do this before we apply global defaults.
375         // Note this is done for backward compatibility.
376         srv_cfg->moveDdnsParams(mutable_cfg);
377 
378         // Move from reservation mode to new reservations flags.
379         // @todo add warning
380         BaseNetworkParser::moveReservationMode(mutable_cfg);
381 
382         // Set all default values if not specified by the user.
383         SimpleParser4::setAllDefaults(mutable_cfg);
384 
385         // And now derive (inherit) global parameters to subnets, if not specified.
386         SimpleParser4::deriveParameters(mutable_cfg);
387 
388         // In principle we could have the following code structured as a series
389         // of long if else if clauses. That would give a marginal performance
390         // boost, but would make the code less readable. We had serious issues
391         // with the parser code debugability, so I decided to keep it as a
392         // series of independent ifs.
393 
394         // This parser is used in several places.
395         Dhcp4ConfigParser global_parser;
396 
397         // Apply global options in the staging config, e.g. ip-reservations-unique
398         global_parser.parseEarly(srv_cfg, mutable_cfg);
399 
400         // We need definitions first
401         ConstElementPtr option_defs = mutable_cfg->get("option-def");
402         if (option_defs) {
403             parameter_name = "option-def";
404             OptionDefListParser parser(AF_INET);
405             CfgOptionDefPtr cfg_option_def = srv_cfg->getCfgOptionDef();
406             parser.parse(cfg_option_def, option_defs);
407         }
408 
409         ConstElementPtr option_datas = mutable_cfg->get("option-data");
410         if (option_datas) {
411             parameter_name = "option-data";
412             OptionDataListParser parser(AF_INET);
413             CfgOptionPtr cfg_option = srv_cfg->getCfgOption();
414             parser.parse(cfg_option, option_datas);
415         }
416 
417         ConstElementPtr control_socket = mutable_cfg->get("control-socket");
418         if (control_socket) {
419             parameter_name = "control-socket";
420             ControlSocketParser parser;
421             parser.parse(*srv_cfg, control_socket);
422         }
423 
424         ConstElementPtr multi_threading = mutable_cfg->get("multi-threading");
425         if (multi_threading) {
426             parameter_name = "multi-threading";
427             MultiThreadingConfigParser parser;
428             parser.parse(*srv_cfg, multi_threading);
429         }
430 
431         ConstElementPtr queue_control = mutable_cfg->get("dhcp-queue-control");
432         if (queue_control) {
433             parameter_name = "dhcp-queue-control";
434             DHCPQueueControlParser parser;
435             srv_cfg->setDHCPQueueControl(parser.parse(queue_control));
436         }
437 
438         ConstElementPtr hr_identifiers =
439             mutable_cfg->get("host-reservation-identifiers");
440         if (hr_identifiers) {
441             parameter_name = "host-reservation-identifiers";
442             HostReservationIdsParser4 parser;
443             parser.parse(hr_identifiers);
444         }
445 
446         ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
447         if (ifaces_config) {
448             parameter_name = "interfaces-config";
449             IfacesConfigParser parser(AF_INET, check_only);
450             CfgIfacePtr cfg_iface = srv_cfg->getCfgIface();
451             parser.parse(cfg_iface, ifaces_config);
452         }
453 
454         ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks");
455         if (sanity_checks) {
456             parameter_name = "sanity-checks";
457             SanityChecksParser parser;
458             parser.parse(*srv_cfg, sanity_checks);
459         }
460 
461         ConstElementPtr expiration_cfg =
462             mutable_cfg->get("expired-leases-processing");
463         if (expiration_cfg) {
464             parameter_name = "expired-leases-processing";
465             ExpirationConfigParser parser;
466             parser.parse(expiration_cfg);
467         }
468 
469         // The hooks-libraries configuration must be parsed after parsing
470         // multi-threading configuration so that libraries are checked
471         // for multi-threading compatibility.
472         ConstElementPtr hooks_libraries = mutable_cfg->get("hooks-libraries");
473         if (hooks_libraries) {
474             parameter_name = "hooks-libraries";
475             HooksLibrariesParser hooks_parser;
476             HooksConfig& libraries = srv_cfg->getHooksConfig();
477             hooks_parser.parse(libraries, hooks_libraries);
478             libraries.verifyLibraries(hooks_libraries->getPosition());
479         }
480 
481         // D2 client configuration.
482         D2ClientConfigPtr d2_client_cfg;
483 
484         // Legacy DhcpConfigParser stuff below.
485         ConstElementPtr dhcp_ddns = mutable_cfg->get("dhcp-ddns");
486         if (dhcp_ddns) {
487             parameter_name = "dhcp-ddns";
488             // Apply defaults
489             D2ClientConfigParser::setAllDefaults(dhcp_ddns);
490             D2ClientConfigParser parser;
491             d2_client_cfg = parser.parse(dhcp_ddns);
492         }
493 
494         ConstElementPtr client_classes = mutable_cfg->get("client-classes");
495         if (client_classes) {
496             parameter_name = "client-classes";
497             ClientClassDefListParser parser;
498             ClientClassDictionaryPtr dictionary =
499                 parser.parse(client_classes, AF_INET);
500             srv_cfg->setClientClassDictionary(dictionary);
501         }
502 
503         // Please move at the end when migration will be finished.
504         ConstElementPtr lease_database = mutable_cfg->get("lease-database");
505         if (lease_database) {
506             parameter_name = "lease-database";
507             db::DbAccessParser parser;
508             std::string access_string;
509             parser.parse(access_string, lease_database);
510             CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
511             cfg_db_access->setLeaseDbAccessString(access_string);
512         }
513 
514         ConstElementPtr hosts_database = mutable_cfg->get("hosts-database");
515         if (hosts_database) {
516             parameter_name = "hosts-database";
517             db::DbAccessParser parser;
518             std::string access_string;
519             parser.parse(access_string, hosts_database);
520             CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
521             cfg_db_access->setHostDbAccessString(access_string);
522         }
523 
524         ConstElementPtr hosts_databases = mutable_cfg->get("hosts-databases");
525         if (hosts_databases) {
526             parameter_name = "hosts-databases";
527             CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
528             db::DbAccessParser parser;
529             for (auto it : hosts_databases->listValue()) {
530                 std::string access_string;
531                 parser.parse(access_string, it);
532                 cfg_db_access->setHostDbAccessString(access_string);
533             }
534         }
535 
536         // Keep relative orders of shared networks and subnets.
537         ConstElementPtr shared_networks = mutable_cfg->get("shared-networks");
538         if (shared_networks) {
539             parameter_name = "shared-networks";
540             /// We need to create instance of SharedNetworks4ListParser
541             /// and parse the list of the shared networks into the
542             /// CfgSharedNetworks4 object. One additional step is then to
543             /// add subnets from the CfgSharedNetworks4 into CfgSubnets4
544             /// as well.
545             SharedNetworks4ListParser parser;
546             CfgSharedNetworks4Ptr cfg = srv_cfg->getCfgSharedNetworks4();
547             parser.parse(cfg, shared_networks);
548 
549             // We also need to put the subnets it contains into normal
550             // subnets list.
551             global_parser.copySubnets4(srv_cfg->getCfgSubnets4(), cfg);
552         }
553 
554         ConstElementPtr subnet4 = mutable_cfg->get("subnet4");
555         if (subnet4) {
556             parameter_name = "subnet4";
557             Subnets4ListConfigParser subnets_parser;
558             // parse() returns number of subnets parsed. We may log it one day.
559             subnets_parser.parse(srv_cfg, subnet4);
560         }
561 
562         ConstElementPtr reservations = mutable_cfg->get("reservations");
563         if (reservations) {
564             parameter_name = "reservations";
565             HostCollection hosts;
566             HostReservationsListParser<HostReservationParser4> parser;
567             parser.parse(SUBNET_ID_GLOBAL, reservations, hosts);
568             for (auto h = hosts.begin(); h != hosts.end(); ++h) {
569                 srv_cfg->getCfgHosts()->add(*h);
570             }
571         }
572 
573         ConstElementPtr config_control = mutable_cfg->get("config-control");
574         if (config_control) {
575             parameter_name = "config-control";
576             ConfigControlParser parser;
577             ConfigControlInfoPtr config_ctl_info = parser.parse(config_control);
578             CfgMgr::instance().getStagingCfg()->setConfigControlInfo(config_ctl_info);
579         }
580 
581         ConstElementPtr compatibility = mutable_cfg->get("compatibility");
582         if (compatibility) {
583             for (auto kv : compatibility->mapValue()) {
584                 if (kv.first == "lenient-option-parsing") {
585                     CfgMgr::instance().getStagingCfg()->setLenientOptionParsing(
586                         kv.second->boolValue());
587                 }
588             }
589         }
590 
591         // Make parsers grouping.
592         ConfigPair config_pair;
593         const std::map<std::string, ConstElementPtr>& values_map =
594             mutable_cfg->mapValue();
595 
596         BOOST_FOREACH(config_pair, values_map) {
597 
598             parameter_name = config_pair.first;
599 
600             // These are converted to SimpleParser and are handled already above.
601             if ((config_pair.first == "option-def") ||
602                 (config_pair.first == "option-data") ||
603                 (config_pair.first == "control-socket") ||
604                 (config_pair.first == "multi-threading") ||
605                 (config_pair.first == "dhcp-queue-control") ||
606                 (config_pair.first == "host-reservation-identifiers") ||
607                 (config_pair.first == "interfaces-config") ||
608                 (config_pair.first == "sanity-checks") ||
609                 (config_pair.first == "expired-leases-processing") ||
610                 (config_pair.first == "hooks-libraries") ||
611                 (config_pair.first == "dhcp-ddns") ||
612                 (config_pair.first == "client-classes") ||
613                 (config_pair.first == "lease-database") ||
614                 (config_pair.first == "hosts-database") ||
615                 (config_pair.first == "hosts-databases") ||
616                 (config_pair.first == "subnet4") ||
617                 (config_pair.first == "shared-networks") ||
618                 (config_pair.first == "reservations") ||
619                 (config_pair.first == "config-control") ||
620                 (config_pair.first == "compatibility")) {
621                 continue;
622             }
623 
624             // As of Kea 1.6.0 we have two ways of inheriting the global parameters.
625             // The old method is used in JSON configuration parsers when the global
626             // parameters are derived into the subnets and shared networks and are
627             // being treated as explicitly specified. The new way used by the config
628             // backend is the dynamic inheritance whereby each subnet and shared
629             // network uses a callback function to return global parameter if it
630             // is not specified at lower level. This callback uses configured globals.
631             // We deliberately include both default and explicitly specified globals
632             // so as the callback can access the appropriate global values regardless
633             // whether they are set to a default or other value.
634             if ( (config_pair.first == "renew-timer") ||
635                  (config_pair.first == "rebind-timer") ||
636                  (config_pair.first == "valid-lifetime") ||
637                  (config_pair.first == "min-valid-lifetime") ||
638                  (config_pair.first == "max-valid-lifetime") ||
639                  (config_pair.first == "decline-probation-period") ||
640                  (config_pair.first == "dhcp4o6-port") ||
641                  (config_pair.first == "echo-client-id") ||
642                  (config_pair.first == "match-client-id") ||
643                  (config_pair.first == "authoritative") ||
644                  (config_pair.first == "next-server") ||
645                  (config_pair.first == "server-hostname") ||
646                  (config_pair.first == "boot-file-name") ||
647                  (config_pair.first == "server-tag") ||
648                  (config_pair.first == "reservation-mode") ||
649                  (config_pair.first == "reservations-global") ||
650                  (config_pair.first == "reservations-in-subnet") ||
651                  (config_pair.first == "reservations-out-of-pool") ||
652                  (config_pair.first == "calculate-tee-times") ||
653                  (config_pair.first == "t1-percent") ||
654                  (config_pair.first == "t2-percent") ||
655                  (config_pair.first == "cache-threshold") ||
656                  (config_pair.first == "cache-max-age") ||
657                  (config_pair.first == "loggers") ||
658                  (config_pair.first == "hostname-char-set") ||
659                  (config_pair.first == "hostname-char-replacement") ||
660                  (config_pair.first == "ddns-send-updates") ||
661                  (config_pair.first == "ddns-override-no-update") ||
662                  (config_pair.first == "ddns-override-client-update") ||
663                  (config_pair.first == "ddns-replace-client-name") ||
664                  (config_pair.first == "ddns-generated-prefix") ||
665                  (config_pair.first == "ddns-qualifying-suffix") ||
666                  (config_pair.first == "ddns-update-on-renew") ||
667                  (config_pair.first == "ddns-use-conflict-resolution") ||
668                  (config_pair.first == "store-extended-info") ||
669                  (config_pair.first == "statistic-default-sample-count") ||
670                  (config_pair.first == "statistic-default-sample-age") ||
671                  (config_pair.first == "ip-reservations-unique") ||
672                  (config_pair.first == "parked-packet-limit")) {
673                 CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
674                                                                         config_pair.second);
675                 continue;
676             }
677 
678             // Nothing to configure for the user-context.
679             if (config_pair.first == "user-context") {
680                 continue;
681             }
682 
683             // If we got here, no code handled this parameter, so we bail out.
684             isc_throw(DhcpConfigError,
685                       "unsupported global configuration parameter: " << config_pair.first
686                       << " (" << config_pair.second->getPosition() << ")");
687         }
688 
689         // Reset parameter name.
690         parameter_name = "<post parsing>";
691 
692         // Apply global options in the staging config.
693         global_parser.parse(srv_cfg, mutable_cfg);
694 
695         // This method conducts final sanity checks and tweaks. In particular,
696         // it checks that there is no conflict between plain subnets and those
697         // defined as part of shared networks.
698         global_parser.sanityChecks(srv_cfg, mutable_cfg);
699 
700         // Validate D2 client configuration.
701         if (!d2_client_cfg) {
702             d2_client_cfg.reset(new D2ClientConfig());
703         }
704         d2_client_cfg->validateContents();
705         srv_cfg->setD2ClientConfig(d2_client_cfg);
706     } catch (const isc::Exception& ex) {
707         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
708                   .arg(parameter_name).arg(ex.what());
709         answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
710 
711         // An error occurred, so make sure that we restore original data.
712         rollback = true;
713     } catch (...) {
714         // For things like bad_cast in boost::lexical_cast
715         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(parameter_name);
716         answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
717                                            " processing error");
718 
719         // An error occurred, so make sure that we restore original data.
720         rollback = true;
721     }
722 
723     if (check_only) {
724         rollback = true;
725         if (!answer) {
726             answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS,
727             "Configuration seems sane. Control-socket, hook-libraries, and D2 "
728             "configuration were sanity checked, but not applied.");
729         }
730     }
731 
732     // So far so good, there was no parsing error so let's commit the
733     // configuration. This will add created subnets and option values into
734     // the server's configuration.
735     // This operation should be exception safe but let's make sure.
736     if (!rollback) {
737         try {
738 
739             // Setup the command channel.
740             configureCommandChannel();
741 
742             // No need to commit interface names as this is handled by the
743             // CfgMgr::commit() function.
744 
745             // Apply the staged D2ClientConfig, used to be done by parser commit
746             D2ClientConfigPtr cfg;
747             cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
748             CfgMgr::instance().setD2ClientConfig(cfg);
749 
750             // This occurs last as if it succeeds, there is no easy way to
751             // revert it.  As a result, the failure to commit a subsequent
752             // change causes problems when trying to roll back.
753             HooksManager::prepareUnloadLibraries();
754             static_cast<void>(HooksManager::unloadLibraries());
755             const HooksConfig& libraries =
756                 CfgMgr::instance().getStagingCfg()->getHooksConfig();
757             libraries.loadLibraries();
758         }
759         catch (const isc::Exception& ex) {
760             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
761             answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
762 
763             // An error occurred, so make sure to restore the original data.
764             rollback = true;
765         } catch (...) {
766             // For things like bad_cast in boost::lexical_cast
767             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
768             answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
769                                                " parsing error");
770 
771             // An error occurred, so make sure to restore the original data.
772             rollback = true;
773         }
774     }
775 
776     // Moved from the commit block to add the config backend indication.
777     if (!rollback) {
778         try {
779 
780             // If there are config backends, fetch and merge into staging config
781             server.getCBControl()->databaseConfigFetch(srv_cfg,
782                                                        CBControlDHCPv4::FetchMode::FETCH_ALL);
783         }
784         catch (const isc::Exception& ex) {
785             std::ostringstream err;
786             err << "during update from config backend database: " << ex.what();
787             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
788             answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
789 
790             // An error occurred, so make sure to restore the original data.
791             rollback = true;
792         } catch (...) {
793             // For things like bad_cast in boost::lexical_cast
794             std::ostringstream err;
795             err << "during update from config backend database: "
796                 << "undefined configuration parsing error";
797             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
798             answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
799 
800             // An error occurred, so make sure to restore the original data.
801             rollback = true;
802         }
803     }
804 
805     // Rollback changes as the configuration parsing failed.
806     if (rollback) {
807         // Revert to original configuration of runtime option definitions
808         // in the libdhcp++.
809         LibDHCP::revertRuntimeOptionDefs();
810         return (answer);
811     }
812 
813     LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
814         .arg(CfgMgr::instance().getStagingCfg()->
815              getConfigSummary(SrvConfig::CFGSEL_ALL4));
816 
817     // Everything was fine. Configuration is successful.
818     answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration successful.");
819     return (answer);
820 }
821 
822 }  // namespace dhcp
823 }  // namespace isc
824