Kea 3.1.1
command_callouts.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2025 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>
11#include <cc/data.h>
12#include <cc/simple_parser.h>
13#include <dhcpsrv/cfgmgr.h>
16#include <dhcpsrv/subnet.h>
18#include <hooks/hooks.h>
19#include <util/str.h>
20#include <legal_log_log.h>
23
24#include <sstream>
25#include <unordered_set>
26
27using namespace isc;
28using namespace isc::config;
29using namespace isc::data;
30using namespace isc::dhcp;
31using namespace isc::hooks;
32using namespace isc::util;
33using namespace isc::legal_log;
34using namespace std;
35
36// Functions accessed by the hooks framework use C linkage to avoid the name
37// mangling that accompanies use of the C++ compiler as well as to avoid
38// issues related to namespaces.
39extern "C" {
40
53bool getOptionalString(ConstElementPtr& arguments, const string& name,
54 string& value) {
55 value = "";
56 try {
57 value = SimpleParser::getString(arguments, name);
58 return (true);
59 } catch (...) {
60 // we don't care why
61 }
62
63 return (false);
64}
65
78bool getOptionalInt(ConstElementPtr& arguments, const string& name,
79 int64_t& value) {
80 value = 0;
81 try {
82 value = SimpleParser::getInteger(arguments, name);
83 return (true);
84 } catch (...) {
85 // we don't care why
86 }
87
88 return (false);
89}
90
99bool isPrefix(ConstElementPtr arguments) {
100 string type_str;
101 if (getOptionalString(arguments, "type", type_str)) {
102 return (type_str == "IA_PD" || type_str == "2");
103 }
104
105 return (false);
106}
107
117void addDuration(CalloutHandle& handle, ostringstream& os, ConstElementPtr& arguments) {
118
119 int64_t duration = 0;
120 if (!getOptionalInt(arguments, "valid-lft", duration)) {
121 int64_t expire = 0;
122 if (getOptionalInt(arguments, "expire", expire)) {
123 duration = expire - LegalLogMgrFactory::instance(handle.getCurrentLibrary())->now().tv_sec;
124 }
125 }
126
127 if (duration > 0) {
128 os << " for " << LegalLogMgr::genDurationString(duration);
129 }
130}
131
138void addContext(ostringstream& os, ConstElementPtr& arguments) {
139
140 ConstElementPtr comment = arguments->get("comment");
141 ConstElementPtr ctx = arguments->get("user-context");
142 if (comment) {
143 ElementPtr copied;
144 if (ctx) {
145 copied = copy(ctx, 0);
146 } else {
147 copied = Element::createMap();
148 }
149 copied->set("comment", comment);
150 ctx = copied;
151 }
152 if (ctx) {
153 os << ", context: " << ctx->str();
154 }
155}
156
164 // Check if the subnet identifier has been specified for the command.
165 // In some cases it may be missing, i.e. lease4-del command, when deleting a
166 // lease by an IP address.
167 int64_t subnet_id_value = 0;
168 if (getOptionalInt(arguments, "subnet-id", subnet_id_value) && (subnet_id_value > 0)) {
169 // The subnet identifier is present and valid.
170 SubnetID subnet_id = static_cast<SubnetID>(subnet_id_value);
171
172 CfgSubnets4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
173 // Get the subnet by id and if the subnet is present, check if its user
174 // context to see if legal logging is enabled for this subnet.
175 ConstSubnet4Ptr subnet = cfg->getBySubnetId(subnet_id);
176 if (isLoggingDisabled(subnet)) {
177 return (false);
178 }
179 }
180 return (true);
181}
182
189int handleLease4Cmds(CalloutHandle& handle, string& name, ConstElementPtr& arguments,
190 ConstElementPtr& /*response*/) {
194 return (1);
195 }
196 try {
197 if (!checkLoggingEnabledSubnet4(arguments)) {
198 return (0);
199 }
200 ostringstream os;
201 ostringstream osa;
202 string origin;
203 getOptionalString(arguments, "origin", origin);
204 if (origin == "ha-partner") {
205 os << "HA partner";
206 } else {
207 os << "Administrator";
208 }
209 if ((name == "lease4-add") || name == "lease4-update") {
210 if (name == "lease4-add") {
211 os << " added a lease of address: ";
212 } else {
213 os << " updated information on the lease of address: ";
214 }
215 osa << SimpleParser::getString(arguments, "ip-address");
216 os << SimpleParser::getString(arguments, "ip-address")
217 << " to a device with hardware address: "
218 << SimpleParser::getString(arguments, "hw-address");
219
220 string client_id;
221 if (getOptionalString(arguments, "client-id", client_id)) {
222 os << ", client-id: " << client_id;
223 // It is not uncommon to provide a printable client ID
224 try {
225 auto bin = ClientId::fromText(client_id)->getClientId();
226 if (str::isPrintable(bin)) {
227 os << " (" << LegalLogMgr::vectorDump(bin) << ")";
228 }
229 } catch (...) {
230 // Ignore any error
231 }
232 }
233
234 addDuration(handle, os, arguments);
235 addContext(os, arguments);
236 } else if (name == "lease4-del") {
237 string ip_address;
238 if (getOptionalString(arguments, "ip-address", ip_address)) {
239 osa << SimpleParser::getString(arguments, "ip-address");
240 os << " deleted the lease for address: "
241 << SimpleParser::getString(arguments, "ip-address");
242 } else {
243 os << " deleted a lease for a device identified by: "
244 << SimpleParser::getString(arguments, "identifier-type")
245 << " of " << SimpleParser::getString(arguments, "identifier");
246 }
247 }
248
249 LegalLogMgrFactory::instance(handle.getCurrentLibrary())->writeln(os.str(), osa.str());
250 } catch (const exception& ex) {
252 .arg(ex.what());
253 return (1);
254 }
255 return (0);
256}
257
265 // Check if the subnet identifier has been specified for the command.
266 // In some cases it may be missing, i.e. lease6-del command, when deleting a
267 // lease by an IP address or prefix or lease6-bulk-apply command.
268 int64_t subnet_id_value = 0;
269 if (getOptionalInt(arguments, "subnet-id", subnet_id_value) && (subnet_id_value > 0)) {
270 // The subnet identifier is present and valid.
271 SubnetID subnet_id = static_cast<SubnetID>(subnet_id_value);
272
273 CfgSubnets6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
274 // Get the subnet by id and if the subnet is present, check if its user
275 // context to see if legal logging is enabled for this subnet.
276 ConstSubnet6Ptr subnet = cfg->getBySubnetId(subnet_id);
277 if (isLoggingDisabled(subnet)) {
278 return (false);
279 }
280 }
281 return (true);
282}
283
290int handleLease6Cmds(CalloutHandle& handle, string& name, ConstElementPtr& arguments,
291 ConstElementPtr& response) {
295 return (1);
296 }
297 try {
298 if (!checkLoggingEnabledSubnet6(arguments)) {
299 return (0);
300 }
301 ostringstream os;
302 ostringstream osa;
303 string origin;
304 getOptionalString(arguments, "origin", origin);
305 if (origin == "ha-partner") {
306 os << "HA partner";
307 } else {
308 os << "Administrator";
309 }
310 if ((name == "lease6-add") || name == "lease6-update") {
311 if (name == "lease6-add") {
312 os << " added a lease of";
313 } else {
314 os << " updated information on the lease of";
315 }
316
317 osa << SimpleParser::getString(arguments, "ip-address");
318 if (isPrefix(arguments)) {
319 os << " prefix: "
320 << SimpleParser::getString(arguments, "ip-address")
321 << "/"
322 << SimpleParser::getInteger(arguments, "prefix-len");
323 osa << "/"
324 << SimpleParser::getInteger(arguments, "prefix-len");
325 } else {
326 os << " address: "
327 << SimpleParser::getString(arguments, "ip-address");
328 }
329
330 os << " to a device with DUID: "
331 << SimpleParser::getString(arguments, "duid");
332
333 string hw_address;
334 if (getOptionalString(arguments, "hw-address", hw_address)) {
335 os << ", hardware address: " << hw_address;
336 }
337
338 addDuration(handle, os, arguments);
339 addContext(os, arguments);
340 } else if (name == "lease6-del") {
341 string ip_address;
342 if (getOptionalString(arguments, "ip-address", ip_address)) {
343 osa << SimpleParser::getString(arguments, "ip-address");
344 os << " deleted the lease for address: "
345 << SimpleParser::getString(arguments, "ip-address");
346 } else {
347 os << " deleted a lease for a device identified by: "
348 << SimpleParser::getString(arguments, "identifier-type")
349 << " of " << SimpleParser::getString(arguments, "identifier");
350 }
351 } else if (name == "lease6-bulk-apply") {
352 // At least one of the 'deleted-leases' or 'leases' must be present.
353 auto deleted_leases = arguments->get("deleted-leases");
354 auto leases = arguments->get("leases");
355
356 if (!deleted_leases && !leases) {
357 isc_throw(BadValue, "neither 'deleted-leases' nor 'leases' parameter"
358 " specified");
359 }
360
361 // Make sure that 'deleted-leases' is a list, if present.
362 if (deleted_leases && (deleted_leases->getType() != Element::list)) {
363 isc_throw(BadValue, "the 'deleted-leases' parameter must be a list");
364 }
365
366 // Make sure that 'leases' is a list, if present.
367 if (leases && (leases->getType() != Element::list)) {
368 isc_throw(BadValue, "the 'leases' parameter must be a list");
369 }
370
371 ConstElementPtr failed_deleted_leases;
372 ConstElementPtr failed_leases;
373
374 auto response_args = response->get("arguments");
375
376 if (response_args) {
377 failed_deleted_leases = response_args->get("failed-deleted-leases");
378 failed_leases = response_args->get("failed-leases");
379 }
380
381 int status = 0;
382
383 if (deleted_leases) {
384 unordered_set<string> failed_deleted_leases_set;
385 if (failed_deleted_leases) {
386 // Make sure that 'failed-deleted-leases' is a list, if present.
387 if (failed_deleted_leases->getType() != Element::list) {
388 isc_throw(BadValue, "the 'failed-deleted-leases' parameter must be a list");
389 }
390 auto leases_list = failed_deleted_leases->listValue();
391
392 for (auto const& lease_params : leases_list) {
393 auto address = lease_params->get("ip-address");
394 if (address) {
395 failed_deleted_leases_set.emplace(address->stringValue());
396 }
397 }
398 }
399
400 auto leases_list = deleted_leases->listValue();
401 for (auto const& lease_params : leases_list) {
402 auto address = lease_params->get("ip-address");
403 if (address && failed_deleted_leases_set.count(address->stringValue()) == 0) {
405 if (!origin.empty()) {
406 copy = data::copy(lease_params);
407 copy->set("origin", Element::create(origin));
408 } else {
409 copy = lease_params;
410 }
411 ConstElementPtr args(copy);
412 ConstElementPtr resp;
413 string cmd_name("lease6-del");
414 int result = handleLease6Cmds(handle, cmd_name, args, resp);
415 if (result) {
416 status = result;
417 }
418 }
419 }
420 }
421
422 if (leases) {
423 unordered_set<string> failed_leases_set;
424 if (failed_leases) {
425 // Make sure that 'failed-leases' is a list, if present.
426 if (failed_leases->getType() != Element::list) {
427 isc_throw(BadValue, "the 'failed-leases' parameter must be a list");
428 }
429 auto leases_list = failed_leases->listValue();
430
431 for (auto const& lease_params : leases_list) {
432 auto address = lease_params->get("ip-address");
433 if (address) {
434 failed_leases_set.emplace(address->stringValue());
435 }
436 }
437 }
438 auto leases_list = leases->listValue();
439 for (auto const& lease_params : leases_list) {
440 auto address = lease_params->get("ip-address");
441 if (address && failed_leases_set.count(address->stringValue()) == 0) {
443 if (!origin.empty()) {
444 copy = data::copy(lease_params);
445 copy->set("origin", Element::create(origin));
446 } else {
447 copy = lease_params;
448 }
449 ConstElementPtr args(copy);
450 ConstElementPtr resp;
451 string cmd_name("lease6-update");
452 int result = handleLease6Cmds(handle, cmd_name, args, resp);
453 if (result) {
454 status = result;
455 }
456 }
457 }
458 }
459 return (status);
460 }
461
462 LegalLogMgrFactory::instance(handle.getCurrentLibrary())->writeln(os.str(), osa.str());
463 } catch (const exception& ex) {
465 .arg(ex.what());
466 return (1);
467 }
468 return (0);
469}
470
489 return (1);
490 }
491
492 string name;
493 ConstElementPtr arguments;
494 ConstElementPtr response;
495 try {
496 handle.getArgument("name", name);
497 handle.getArgument("arguments", arguments);
498 handle.getArgument("response", response);
499
500 int result = SimpleParser::getInteger(response, "result");
501 if (result != 0) {
502 // We don't log failed commands
503 return (0);
504 }
505
506 // We are only interested in the following commands.
507 static unordered_set<string> const supported = {
508 "lease4-add", "lease4-update","lease4-del", "lease6-add",
509 "lease6-update", "lease6-del", "lease6-bulk-apply"
510 };
511 if (supported.count(name) == 0) {
512 return (0);
513 }
514
515 // We need to get a different configuration pointer depending if we're
516 // in v4 or v6 universe. The easiest way to check the universe is by the
517 // command name.
518 if (name.find("lease4-") != string::npos) {
519 return (handleLease4Cmds(handle, name, arguments, response));
520 } else if (name.find("lease6-") != string::npos) {
521 return (handleLease6Cmds(handle, name, arguments, response));
522 }
523
524 } catch (const exception& ex) {
526 .arg(ex.what());
527 return (1);
528 }
529
530 return (0);
531}
532
533} // end extern "C"
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition data.cc:249
@ list
Definition data.h:146
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:304
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, int level)
Copy the data up to a nesting level.
Definition data.cc:1420
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
boost::shared_ptr< const Subnet6 > ConstSubnet6Ptr
A const pointer to a Subnet6 object.
Definition subnet.h:623
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition subnet.h:458
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.