Kea 3.1.1
legal_log_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2020-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
7#include <config.h>
8
9#include <legal_log_mgr.h>
11
13#include <dhcpsrv/cfgmgr.h>
14#include <dhcpsrv/dhcpsrv_log.h>
15#include <eval/eval_context.h>
16#include <util/reconnect_ctl.h>
17#include <util/filesystem.h>
18
19#include <boost/date_time/posix_time/posix_time.hpp>
20
21#include <errno.h>
22#include <iostream>
23#include <sstream>
24#include <time.h>
25
26namespace isc {
27namespace dhcp {
28
29using namespace isc::asiolink;
30using namespace isc::data;
31using namespace isc::db;
32using namespace isc::dhcp;
33using namespace isc::eval;
34using namespace isc::hooks;
35using namespace isc::util;
36using namespace isc::util::file;
37using namespace std;
38
39namespace {
40 // Singleton PathChecker to set and hold valid legal log path.
41 file::PathCheckerPtr legal_log_path_checker_;
42};
43
44void
46 if (!parameters || !parameters->get("type") ||
47 parameters->get("type")->stringValue() == "logfile") {
48 parseFile(parameters, map);
49 } else if (parameters->get("type")->stringValue() == "syslog") {
50 parseSyslog(parameters, map);
51 } else {
52 parseDatabase(parameters, map);
53 }
54 parseExtraParameters(parameters, map);
55}
56
57void
59 // Should never happen with the code flow at the time of writing, but
60 // let's get this check out of the way.
61 if (!parameters) {
62 isc_throw(BadValue, "no parameters specified for the hook library");
63 }
64
66
67 // Strings
68 for (char const* const& key : {
69 "type", "user", "password", "host", "name", "trust-anchor",
70 "cert-file", "key-file", "ssl-mode", "cipher-list" }) {
71 ConstElementPtr const value(parameters->get(key));
72 if (value) {
73 db_parameters.emplace(key, value->stringValue());
74 }
75 }
76
77 // uint32_t
78 for (char const* const& key : {
79 "connect-timeout", "reconnect-wait-time", "max-reconnect-tries",
80 "read-timeout", "write-timeout", "tcp-user-timeout"}) {
81 ConstElementPtr const value(parameters->get(key));
82 if (value) {
83 int64_t integer_value(value->intValue());
84 auto const max(numeric_limits<uint32_t>::max());
85 if (integer_value < 0 || max < integer_value) {
87 key << " value: " << integer_value
88 << " is out of range, expected value: 0.."
89 << max);
90 }
91 db_parameters[key] =
92 boost::lexical_cast<string>(integer_value);
93 }
94 }
95
96 string param_name = "tcp-nodelay";
97 ConstElementPtr param = parameters->get(param_name);
98 if (param) {
99 db_parameters.emplace(param_name,
100 param->boolValue() ? "true" : "false");
101 }
102
103 // Always set "on-fail" to "serve-retry-continue" if not explicitly
104 // configured.
106 param_name = "on-fail";
107 param = parameters->get(param_name);
108 if (param) {
109 on_fail_action = param->stringValue();
111 }
112 db_parameters.emplace(param_name, on_fail_action);
113
114 param_name = "retry-on-startup";
115 param = parameters->get(param_name);
116 if (param) {
117 db_parameters.emplace(param_name,
118 param->boolValue() ? "true" : "false");
119 }
120
121 int64_t port = 0;
122 param_name = "port";
123 param = parameters->get(param_name);
124 if (param) {
125 port = param->intValue();
126 if ((port < 0) || (port > numeric_limits<uint16_t>::max())) {
127 isc_throw(OutOfRange, param_name << " value: " << port
128 << " is out of range, expected value: 0.."
129 << numeric_limits<uint16_t>::max());
130 }
131 db_parameters.emplace(param_name,
132 boost::lexical_cast<string>(port));
133 }
134
135 string redacted =
137
138 string const db_type(db_parameters["type"]);
139 map = db_parameters;
140}
141
142void
144 // Should never happen with the code flow at the time of writing, but
145 // let's get this check out of the way.
146 if (!parameters) {
147 isc_throw(BadValue, "no parameters specified for the hook library");
148 }
149
150 DatabaseConnection::ParameterMap syslog_parameters;
151
152 // Strings
153 for (char const* const& key : { "type", "pattern", "facility" }) {
154 ConstElementPtr const value(parameters->get(key));
155 if (value) {
156 syslog_parameters.emplace(key, value->stringValue());
157 }
158 }
159
160 map = syslog_parameters;
161}
162
163void
165 DatabaseConnection::ParameterMap file_parameters;
166 file_parameters["type"] = "logfile";
167
168 if (!parameters) {
169 map = file_parameters;
170 return;
171 }
172
173 // Strings
174 for (char const* const& key : { "path", "base-name", "time-unit", "prerotate", "postrotate" }) {
175 ConstElementPtr const value(parameters->get(key));
176 if (value) {
177 if (key == std::string("path")) {
178 try {
179 auto valid_path = validatePath(value->stringValue());
180 file_parameters.emplace(key, valid_path);
181 } catch (const SecurityWarn& ex) {
183 .arg(ex.what());
184 file_parameters.emplace(key, value->stringValue());
185 }
186 }
187
188 file_parameters.emplace(key, value->stringValue());
189 }
190 }
191
192 // uint32_t
193 for (char const* const& key : { "count" }) {
194 ConstElementPtr const value(parameters->get(key));
195 if (value) {
196 int64_t integer_value(value->intValue());
197 auto const max(numeric_limits<uint32_t>::max());
198 if (integer_value < 0 || max < integer_value) {
200 key << " value: " << integer_value
201 << " is out of range, expected value: 0.."
202 << max);
203 }
204 file_parameters[key] = boost::lexical_cast<string>(integer_value);
205 }
206 }
207 map = file_parameters;
208}
209
210void
212 if (!parameters) {
213 return;
214 }
215
216 // Strings
217 for (char const* const& key : { "request-parser-format", "response-parser-format", "timestamp-format" }) {
218 ConstElementPtr const value(parameters->get(key));
219 if (value && !value->stringValue().empty()) {
220 map.emplace(key, value->stringValue());
221 }
222 }
223}
224
225struct tm
227 struct tm time_info;
228 struct timespec timestamp = now();
229 localtime_r(&timestamp.tv_sec, &time_info);
230 return (time_info);
231}
232
233struct timespec
234LegalLogMgr::now() const {
235 struct timespec now;
236 clock_gettime(CLOCK_REALTIME, &now);
237 return (now);
238}
239
240string
242 // Get a text representation of the current time.
243 return (getNowString(timestamp_format_));
244}
245
246string
247LegalLogMgr::getNowString(const string& format) const {
248 // Get a text representation of the current time.
249 return (getTimeString(now(), format));
250}
251
252string
253LegalLogMgr::getTimeString(const struct timespec& time, const string& format) {
254 // Get a text representation of the requested time.
255
256 // First a quick and dirty support for fractional seconds: Replace any "%Q"
257 // tokens in the format string with the microsecond count from the timespec,
258 // before handing it off to strftime().
259 string tmp_format = format;
260 for (auto it = tmp_format.begin(); it < tmp_format.end(); ++it) {
261 if (*it == '%' && ((it + 1) < tmp_format.end())) {
262 if (*(it + 1) == 'Q') {
263 // Save the current position.
264 string::size_type pos = it - tmp_format.begin();
265 // Render the microsecond count.
266 ostringstream usec;
267 usec << setw(6) << setfill('0') << (time.tv_nsec / 1000);
268 string microseconds = usec.str();
269 microseconds.insert(3, 1, '.');
270 tmp_format.replace(it, it + 2, microseconds);
271 // Reinitialize the iterator after manipulating the string.
272 it = tmp_format.begin() + pos + microseconds.length() - 1;
273 } else {
274 ++it;
275 }
276 }
277 }
278
279 char buffer[128];
280 struct tm time_info;
281 localtime_r(&time.tv_sec, &time_info);
282
283 if (!strftime(buffer, sizeof(buffer), tmp_format.c_str(), &time_info)) {
285 "strftime returned 0. Maybe the timestamp format '"
286 << tmp_format
287 << "' result is too long, maximum length allowed: "
288 << sizeof(buffer));
289 }
290 return (string(buffer));
291}
292
293string
294LegalLogMgr::genDurationString(const uint32_t secs) {
295 // Because Kea handles lease lifetimes as uint32_t and supports
296 // a value of 0xFFFFFFFF (infinite lifetime), we don't use things like
297 // boost:posix_time::time_duration as they work on longs. Therefore
298 // we'll figure it out ourselves. Besides, the math ain't that hard.
299 if (secs == 0xffffffff) {
300 return ("infinite duration");
301 }
302
303 uint32_t seconds = secs % 60;
304 uint32_t remainder = secs / 60;
305 uint32_t minutes = remainder % 60;
306 remainder /= 60;
307 uint32_t hours = remainder % 24;
308 uint32_t days = remainder / 24;
309
310 ostringstream os;
311 // Only spit out days if we have em.
312 if (days) {
313 os << days << " days ";
314 }
315
316 os << hours << " hrs "
317 << minutes << " mins "
318 << seconds << " secs";
319
320 return (os.str());
321}
322
323string
324LegalLogMgr::vectorHexDump(const vector<uint8_t>& bytes,
325 const string& delimiter) {
326 stringstream tmp;
327 tmp << hex;
328 bool delim = false;
329 for (auto const& it : bytes) {
330 if (delim) {
331 tmp << delimiter;
332 }
333 tmp << setw(2) << setfill('0') << static_cast<unsigned int>(it);
334 delim = true;
335 }
336 return (tmp.str());
337}
338
339string
340LegalLogMgr::vectorDump(const vector<uint8_t>& bytes) {
341 if (bytes.empty()) {
342 return (string());
343 }
344 return (string(bytes.cbegin(), bytes.cend()));
345}
346
347void
348LegalLogMgr::setRequestFormatExpression(const string& extended_format) {
349 Option::Universe universe;
350 if (CfgMgr::instance().getFamily() == AF_INET) {
351 universe = Option::V4;
352 } else {
353 universe = Option::V6;
354 }
355 EvalContext eval_ctx(universe);
356 eval_ctx.parseString(extended_format, EvalContext::PARSER_STRING);
357 request_expression_.reset(new Expression(eval_ctx.expression_));
358}
359
360void
361LegalLogMgr::setResponseFormatExpression(const string& extended_format) {
362 Option::Universe universe;
363 if (CfgMgr::instance().getFamily() == AF_INET) {
364 universe = Option::V4;
365 } else {
366 universe = Option::V6;
367 }
368 EvalContext eval_ctx(universe);
369 eval_ctx.parseString(extended_format, EvalContext::PARSER_STRING);
370 response_expression_.reset(new Expression(eval_ctx.expression_));
371}
372
373void
374LegalLogMgr::setTimestampFormat(const string& timestamp_format) {
375 timestamp_format_ = timestamp_format;
376}
377
378const string
380 switch (action) {
381 case Action::ASSIGN:
382 return ("assigned");
383 case Action::RELEASE:
384 return ("released");
385 default:
386 return ("unknown-action");
387 }
388}
389
390std::string
391LegalLogMgr::getLogPath(bool reset /* = false */, const std::string explicit_path /* = "" */) {
392 if (!legal_log_path_checker_ || reset) {
393 legal_log_path_checker_.reset(new file::PathChecker(LEGAL_LOG_DIR, "KEA_LEGAL_LOG_DIR"));
394 if (!explicit_path.empty()) {
395 legal_log_path_checker_->getPath(true, explicit_path);
396 }
397 }
398
399 return (legal_log_path_checker_->getPath());
400}
401
402std::string
403LegalLogMgr::validatePath(const std::string logpath) {
404 if (!legal_log_path_checker_) {
405 getLogPath();
406 }
407
408 return (legal_log_path_checker_->validateDirectory(logpath));
409}
410
411} // namespace dhcp
412} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
static std::string redactedAccessString(const ParameterMap &parameters)
Redact database access string.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
Thrown if a LegalLogMgr encounters an error.
virtual struct tm currentTimeInfo() const
Returns the current local date and time.
void setRequestFormatExpression(const std::string &extended_format)
Sets request extended format expression for custom logging.
static void parseSyslog(const isc::data::ConstElementPtr &parameters, isc::db::DatabaseConnection::ParameterMap &map)
Parse syslog specification.
static void parseFile(const isc::data::ConstElementPtr &parameters, isc::db::DatabaseConnection::ParameterMap &map)
Parse file specification.
static std::string vectorHexDump(const std::vector< uint8_t > &bytes, const std::string &delimiter=":")
Creates a string of hex digit pairs from a vector of bytes.
static void parseDatabase(const isc::data::ConstElementPtr &parameters, isc::db::DatabaseConnection::ParameterMap &map)
Parse database specification.
void setResponseFormatExpression(const std::string &extended_format)
Sets response extended format expression for custom logging.
static std::string genDurationString(const uint32_t secs)
Translates seconds into a text string of days, hours, minutes and seconds.
static std::string validatePath(const std::string logpath)
Validates a log path against the supported path for legal log files.
virtual struct timespec now() const
Returns the current system time.
static std::string getTimeString(const struct timespec &time, const std::string &format)
Returns a time as string.
static std::string getLogPath(bool reset=false, const std::string explicit_path="")
Fetches the supported legal log file path.
static std::string vectorDump(const std::vector< uint8_t > &bytes)
Creates a string from a vector of printable bytes.
LegalLogMgr(const isc::db::DatabaseConnection::ParameterMap parameters)
Constructor.
static void parseConfig(const isc::data::ConstElementPtr &parameters, isc::db::DatabaseConnection::ParameterMap &map)
Parse database specification.
void setTimestampFormat(const std::string &timestamp_format)
Sets the timestamp format used for logging.
static void parseExtraParameters(const isc::data::ConstElementPtr &parameters, isc::db::DatabaseConnection::ParameterMap &map)
Parse extra parameters which are not related to backend connection.
virtual std::string getNowString() const
Returns the current date and time as string.
Universe
defines option universe DHCPv4 or DHCPv6
Definition option.h:90
Evaluation context, an interface to the expression evaluation.
bool parseString(const std::string &str, ParserType type=PARSER_BOOL)
Run the parser on the string specified.
@ PARSER_STRING
expression is expected to evaluate to string
isc::dhcp::Expression expression_
Parsed expression (output tokens are stored here)
static std::string onFailActionToText(OnFailAction action)
Convert action to string.
static OnFailAction onFailActionFromText(const std::string &text)
Convert string to action.
Embodies a supported path against which file paths can be validated.
Definition filesystem.h:203
A generic exception that is thrown if a parameter given violates security check but enforcement is la...
Definition filesystem.h:21
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Defines the abstract class for backend stores.
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition dhcpsrv_log.h:56
const string actionToVerb(Action action)
Translates an Action into its corresponding verb.
const isc::log::MessageID LEGAL_LOG_PATH_SECURITY_WARNING
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition token.h:29
Action
Describe what kind of event is being logged.
boost::shared_ptr< PathChecker > PathCheckerPtr
Defines a pointer to a PathChecker.
Definition filesystem.h:323
Defines the logger used by the top-level component of kea-lfc.