Kea 3.3.3
command_callouts.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2026 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
8
9#include <config.h>
10
12#include <cc/data.h>
13#include <cc/simple_parser.h>
16#include <dhcpsrv/cfgmgr.h>
18#include <dhcpsrv/subnet.h>
21#include <hooks/hooks.h>
22#include <util/str.h>
23
24#include <sstream>
25#include <unordered_set>
26
28
29using namespace isc;
30using namespace isc::config;
31using namespace isc::data;
32using namespace isc::dhcp;
33using namespace isc::hooks;
34using namespace isc::util;
35using namespace isc::legal_log;
36using namespace std;
37
38// Functions accessed by the hooks framework use C linkage to avoid the name
39// mangling that accompanies use of the C++ compiler as well as to avoid
40// issues related to namespaces.
41extern "C" {
42
55bool getOptionalString(ConstElementPtr& arguments, const string& name,
56 string& value) {
57 value = "";
58 try {
59 value = SimpleParser::getString(arguments, name);
60 return (true);
61 } catch (...) {
62 // we don't care why
63 }
64
65 return (false);
66}
67
80bool getOptionalInt(ConstElementPtr& arguments, const string& name,
81 int64_t& value) {
82 value = 0;
83 try {
84 value = SimpleParser::getInteger(arguments, name);
85 return (true);
86 } catch (...) {
87 // we don't care why
88 }
89
90 return (false);
91}
92
101bool isPrefix(ConstElementPtr arguments) {
102 string type_str;
103 if (getOptionalString(arguments, "type", type_str)) {
104 return (type_str == "IA_PD" || type_str == "2");
105 }
106
107 return (false);
108}
109
119void addDuration(CalloutHandle& handle, ostringstream& os, ConstElementPtr& arguments) {
120
121 int64_t duration = 0;
122 if (!getOptionalInt(arguments, "valid-lft", duration)) {
123 int64_t expire = 0;
124 if (getOptionalInt(arguments, "expire", expire)) {
125 duration = expire - LegalLogMgrFactory::instance(handle.getCurrentLibrary())->now().tv_sec;
126 }
127 }
128
129 if (duration > 0) {
130 os << " for " << LegalLogMgr::genDurationString(duration);
131 }
132}
133
140void addContext(ostringstream& os, ConstElementPtr& arguments) {
141
142 ConstElementPtr comment = arguments->get("comment");
143 ConstElementPtr ctx = arguments->get("user-context");
144 if (comment) {
145 ElementPtr copied;
146 if (ctx) {
147 copied = copy(ctx, 0);
148 } else {
149 copied = Element::createMap();
150 }
151 copied->set("comment", comment);
152 ctx = copied;
153 }
154 if (ctx) {
155 os << ", context: " << ctx->str();
156 }
157}
158
166 // Check if the subnet identifier has been specified for the command.
167 // In some cases it may be missing, i.e. lease4-del command, when deleting a
168 // lease by an IP address.
169 int64_t subnet_id_value = 0;
170 if (getOptionalInt(arguments, "subnet-id", subnet_id_value) && (subnet_id_value > 0)) {
171 // The subnet identifier is present and valid.
172 SubnetID subnet_id = static_cast<SubnetID>(subnet_id_value);
173
174 CfgSubnets4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
175 // Get the subnet by id and if the subnet is present, check if its user
176 // context to see if legal logging is enabled for this subnet.
177 ConstSubnet4Ptr subnet = cfg->getBySubnetId(subnet_id);
178 if (isLoggingDisabled(subnet)) {
179 return (false);
180 }
181 }
182 return (true);
183}
184
191int handleLease4Cmds(CalloutHandle& handle, string& name, ConstElementPtr& arguments,
192 ConstElementPtr& /*response*/) {
196 return (1);
197 }
198 try {
199 if (!checkLoggingEnabledSubnet4(arguments)) {
200 return (0);
201 }
202 ostringstream os;
203 ostringstream osa;
204 string origin;
205 getOptionalString(arguments, "origin", origin);
206 if (origin == "ha-partner") {
207 os << "HA partner";
208 } else {
209 os << "Administrator";
210 }
211 if ((name == "lease4-add") || name == "lease4-update") {
212 if (name == "lease4-add") {
213 os << " added a lease of address: ";
214 } else {
215 os << " updated information on the lease of address: ";
216 }
217 osa << SimpleParser::getString(arguments, "ip-address");
218 os << SimpleParser::getString(arguments, "ip-address")
219 << " to a device with hardware address: "
220 << SimpleParser::getString(arguments, "hw-address");
221
222 string client_id;
223 if (getOptionalString(arguments, "client-id", client_id)) {
224 os << ", client-id: " << client_id;
225 // It is not uncommon to provide a printable client ID
226 try {
227 auto bin = ClientId::fromText(client_id)->getClientId();
228 if (str::isPrintable(bin)) {
229 os << " (" << LegalLogMgr::vectorDump(bin) << ")";
230 }
231 } catch (...) {
232 // Ignore any error
233 }
234 }
235
236 addDuration(handle, os, arguments);
237 addContext(os, arguments);
238 } else if (name == "lease4-del") {
239 string ip_address;
240 if (getOptionalString(arguments, "ip-address", ip_address)) {
241 osa << SimpleParser::getString(arguments, "ip-address");
242 os << " deleted the lease for address: "
243 << SimpleParser::getString(arguments, "ip-address");
244 } else {
245 os << " deleted a lease for a device identified by: "
246 << SimpleParser::getString(arguments, "identifier-type")
247 << " of " << SimpleParser::getString(arguments, "identifier");
248 }
249 }
250
251 LegalLogMgrFactory::instance(handle.getCurrentLibrary())->writeln(os.str(), osa.str());
252 } catch (const exception& ex) {
254 .arg(ex.what());
255 return (1);
256 }
257 return (0);
258}
259
267 // Check if the subnet identifier has been specified for the command.
268 // In some cases it may be missing, i.e. lease6-del command, when deleting a
269 // lease by an IP address or prefix or lease6-bulk-apply command.
270 int64_t subnet_id_value = 0;
271 if (getOptionalInt(arguments, "subnet-id", subnet_id_value) && (subnet_id_value > 0)) {
272 // The subnet identifier is present and valid.
273 SubnetID subnet_id = static_cast<SubnetID>(subnet_id_value);
274
275 CfgSubnets6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
276 // Get the subnet by id and if the subnet is present, check if its user
277 // context to see if legal logging is enabled for this subnet.
278 ConstSubnet6Ptr subnet = cfg->getBySubnetId(subnet_id);
279 if (isLoggingDisabled(subnet)) {
280 return (false);
281 }
282 }
283 return (true);
284}
285
292int handleLease6Cmds(CalloutHandle& handle, string& name, ConstElementPtr& arguments,
293 ConstElementPtr& response) {
297 return (1);
298 }
299 try {
300 if (!checkLoggingEnabledSubnet6(arguments)) {
301 return (0);
302 }
303 ostringstream os;
304 ostringstream osa;
305 string origin;
306 getOptionalString(arguments, "origin", origin);
307 if (origin == "ha-partner") {
308 os << "HA partner";
309 } else {
310 os << "Administrator";
311 }
312 if ((name == "lease6-add") || name == "lease6-update") {
313 if (name == "lease6-add") {
314 os << " added a lease of";
315 } else {
316 os << " updated information on the lease of";
317 }
318
319 osa << SimpleParser::getString(arguments, "ip-address");
320 if (isPrefix(arguments)) {
321 os << " prefix: "
322 << SimpleParser::getString(arguments, "ip-address")
323 << "/"
324 << SimpleParser::getInteger(arguments, "prefix-len");
325 osa << "/"
326 << SimpleParser::getInteger(arguments, "prefix-len");
327 } else {
328 os << " address: "
329 << SimpleParser::getString(arguments, "ip-address");
330 }
331
332 os << " to a device with DUID: "
333 << SimpleParser::getString(arguments, "duid");
334
335 string hw_address;
336 if (getOptionalString(arguments, "hw-address", hw_address)) {
337 os << ", hardware address: " << hw_address;
338 }
339
340 addDuration(handle, os, arguments);
341 addContext(os, arguments);
342 } else if (name == "lease6-del") {
343 string ip_address;
344 if (getOptionalString(arguments, "ip-address", ip_address)) {
345 osa << SimpleParser::getString(arguments, "ip-address");
346 os << " deleted the lease for address: "
347 << SimpleParser::getString(arguments, "ip-address");
348 } else {
349 os << " deleted a lease for a device identified by: "
350 << SimpleParser::getString(arguments, "identifier-type")
351 << " of " << SimpleParser::getString(arguments, "identifier");
352 }
353 } else if (name == "lease6-bulk-apply") {
354 // At least one of the 'deleted-leases' or 'leases' must be present.
355 auto deleted_leases = arguments->get("deleted-leases");
356 auto leases = arguments->get("leases");
357
358 if (!deleted_leases && !leases) {
359 isc_throw(BadValue, "neither 'deleted-leases' nor 'leases' parameter"
360 " specified");
361 }
362
363 // Make sure that 'deleted-leases' is a list, if present.
364 if (deleted_leases && (deleted_leases->getType() != Element::list)) {
365 isc_throw(BadValue, "the 'deleted-leases' parameter must be a list");
366 }
367
368 // Make sure that 'leases' is a list, if present.
369 if (leases && (leases->getType() != Element::list)) {
370 isc_throw(BadValue, "the 'leases' parameter must be a list");
371 }
372
373 ConstElementPtr failed_deleted_leases;
374 ConstElementPtr failed_leases;
375
376 auto response_args = response->get("arguments");
377
378 if (response_args) {
379 failed_deleted_leases = response_args->get("failed-deleted-leases");
380 failed_leases = response_args->get("failed-leases");
381 }
382
383 int status = 0;
384
385 if (deleted_leases) {
386 unordered_set<string> failed_deleted_leases_set;
387 if (failed_deleted_leases) {
388 // Make sure that 'failed-deleted-leases' is a list, if present.
389 if (failed_deleted_leases->getType() != Element::list) {
390 isc_throw(BadValue, "the 'failed-deleted-leases' parameter must be a list");
391 }
392 auto leases_list = failed_deleted_leases->listValue();
393
394 for (auto const& lease_params : leases_list) {
395 auto address = lease_params->get("ip-address");
396 if (address) {
397 failed_deleted_leases_set.emplace(address->stringValue());
398 }
399 }
400 }
401
402 auto leases_list = deleted_leases->listValue();
403 for (auto const& lease_params : leases_list) {
404 auto address = lease_params->get("ip-address");
405 if (address && failed_deleted_leases_set.count(address->stringValue()) == 0) {
407 if (!origin.empty()) {
408 copy = data::copy(lease_params);
409 copy->set("origin", Element::create(origin));
410 } else {
411 copy = lease_params;
412 }
413 ConstElementPtr args(copy);
414 ConstElementPtr resp;
415 string cmd_name("lease6-del");
416 int result = handleLease6Cmds(handle, cmd_name, args, resp);
417 if (result) {
418 status = result;
419 }
420 }
421 }
422 }
423
424 if (leases) {
425 unordered_set<string> failed_leases_set;
426 if (failed_leases) {
427 // Make sure that 'failed-leases' is a list, if present.
428 if (failed_leases->getType() != Element::list) {
429 isc_throw(BadValue, "the 'failed-leases' parameter must be a list");
430 }
431 auto leases_list = failed_leases->listValue();
432
433 for (auto const& lease_params : leases_list) {
434 auto address = lease_params->get("ip-address");
435 if (address) {
436 failed_leases_set.emplace(address->stringValue());
437 }
438 }
439 }
440 auto leases_list = leases->listValue();
441 for (auto const& lease_params : leases_list) {
442 auto address = lease_params->get("ip-address");
443 if (address && failed_leases_set.count(address->stringValue()) == 0) {
445 if (!origin.empty()) {
446 copy = data::copy(lease_params);
447 copy->set("origin", Element::create(origin));
448 } else {
449 copy = lease_params;
450 }
451 ConstElementPtr args(copy);
452 ConstElementPtr resp;
453 string cmd_name("lease6-update");
454 int result = handleLease6Cmds(handle, cmd_name, args, resp);
455 if (result) {
456 status = result;
457 }
458 }
459 }
460 }
461 return (status);
462 }
463
464 LegalLogMgrFactory::instance(handle.getCurrentLibrary())->writeln(os.str(), osa.str());
465 } catch (const exception& ex) {
467 .arg(ex.what());
468 return (1);
469 }
470 return (0);
471}
472
491 return (1);
492 }
493
494 string name;
495 ConstElementPtr arguments;
496 ConstElementPtr response;
497 try {
498 handle.getArgument("name", name);
499 handle.getArgument("arguments", arguments);
500 handle.getArgument("response", response);
501
502 int result = SimpleParser::getInteger(response, "result");
503 if (result != 0) {
504 // We don't log failed commands
505 return (0);
506 }
507
508 // We are only interested in the following commands.
509 static unordered_set<string> const supported = {
510 "lease4-add", "lease4-update","lease4-del", "lease6-add",
511 "lease6-update", "lease6-del", "lease6-bulk-apply"
512 };
513 if (supported.count(name) == 0) {
514 return (0);
515 }
516
517 // We need to get a different configuration pointer depending if we're
518 // in v4 or v6 universe. The easiest way to check the universe is by the
519 // command name.
520 if (name.find("lease4-") != string::npos) {
521 return (handleLease4Cmds(handle, name, arguments, response));
522 } else if (name.find("lease6-") != string::npos) {
523 return (handleLease6Cmds(handle, name, arguments, response));
524 }
525
526 } catch (const exception& ex) {
528 .arg(ex.what());
529 return (1);
530 }
531
532 return (0);
533}
534
535} // end extern "C"
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:300
@ list
Definition data.h:159
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:355
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
static std::string getString(isc::data::ConstElementPtr scope, const std::string &name)
Returns a string parameter from a scope.
static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string &name)
Returns an integer parameter from a scope.
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition cfgmgr.cc:116
static ClientIdPtr fromText(const std::string &text)
Create client identifier from the textual format.
Definition duid.cc:73
static LegalLogMgrPtr & instance(ManagerID id=0)
Returns the forensic backend manager with specified ID.
static std::string genDurationString(const uint32_t secs)
Translates seconds into a text string of days, hours, minutes and seconds.
static std::string vectorDump(const std::vector< uint8_t > &bytes)
Creates a string from a vector of printable bytes.
Per-packet callout handle.
void getArgument(const std::string &name, T &value) const
Get argument.
int getCurrentLibrary() const
Get current library index.
bool getOptionalInt(ConstElementPtr &arguments, const string &name, int64_t &value)
Fetch value for a integer element if it is an element scope.
int handleLease4Cmds(CalloutHandle &handle, string &name, ConstElementPtr &arguments, ConstElementPtr &)
Handle lease4 related commands.
bool getOptionalString(ConstElementPtr &arguments, const string &name, string &value)
Fetch value for a string element if it is an element scope.
int command_processed(CalloutHandle &handle)
This callout is called at the "command_processed" hook point.
bool isPrefix(ConstElementPtr arguments)
Returns true if an element scope describes a prefix delegation.
void addDuration(CalloutHandle &handle, ostringstream &os, ConstElementPtr &arguments)
Outputs text describing lease duration to a stream.
void addContext(ostringstream &os, ConstElementPtr &arguments)
Outputs text describing lease optional user context to a stream.
bool checkLoggingEnabledSubnet6(ConstElementPtr &arguments)
Check if command has optional subnet-id parameter and if it does, check that the respective subnet ha...
int handleLease6Cmds(CalloutHandle &handle, string &name, ConstElementPtr &arguments, ConstElementPtr &response)
Handle lease6 related commands.
bool checkLoggingEnabledSubnet4(ConstElementPtr &arguments)
Check if command has optional subnet-id parameter and if it does, check that the respective subnet ha...
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const isc::log::MessageID LEGAL_LOG_COMMAND_NO_LEGAL_STORE
const isc::log::MessageID LEGAL_LOG_COMMAND_WRITE_ERROR
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
ElementPtr copy(ConstElementPtr from, unsigned level)
Copy the data up to a nesting level.
Definition data.cc:1543
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
boost::shared_ptr< const Subnet6 > ConstSubnet6Ptr
A const pointer to a Subnet6 object.
Definition subnet.h:620
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition subnet.h:455
boost::shared_ptr< CfgSubnets6 > CfgSubnets6Ptr
Non-const pointer.
uint32_t SubnetID
Defines unique IPv4 or IPv6 subnet identifier.
Definition subnet_id.h:25
boost::shared_ptr< CfgSubnets4 > CfgSubnets4Ptr
Non-const pointer.
isc::log::Logger legal_log_logger("legal-log-hooks")
Legal Log Logger.
bool isLoggingDisabled(const SubnetPtrType &subnet)
Checks if legal logging is disabled for a subnet.
bool isPrintable(const string &content)
Check if a string is printable.
Definition str.cc:310
Defines the logger used by the top-level component of kea-lfc.