Kea 3.2.0-git
legal_log_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2020-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
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 // Always set "on-fail" to "serve-retry-continue" if not explicitly
97 // configured.
99 string param_name = "on-fail";
100 ConstElementPtr param = parameters->get(param_name);
101 if (param) {
102 on_fail_action = param->stringValue();
104 }
105 db_parameters.emplace(param_name, on_fail_action);
106
107 param_name = "retry-on-startup";
108 param = parameters->get(param_name);
109 if (param) {
110 db_parameters.emplace(param_name,
111 param->boolValue() ? "true" : "false");
112 }
113
114 int64_t port = 0;
115 param_name = "port";
116 param = parameters->get(param_name);
117 if (param) {
118 port = param->intValue();
119 if ((port < 0) || (port > numeric_limits<uint16_t>::max())) {
120 isc_throw(OutOfRange, param_name << " value: " << port
121 << " is out of range, expected value: 0.."
122 << numeric_limits<uint16_t>::max());
123 }
124 db_parameters.emplace(param_name,
125 boost::lexical_cast<string>(port));
126 }
127
128 string redacted =
130
131 string const db_type(db_parameters["type"]);
132 map = db_parameters;
133}
134
135void
137 // Should never happen with the code flow at the time of writing, but
138 // let's get this check out of the way.
139 if (!parameters) {
140 isc_throw(BadValue, "no parameters specified for the hook library");
141 }
142
143 DatabaseConnection::ParameterMap syslog_parameters;
144
145 // Strings
146 for (char const* const& key : { "type", "pattern", "facility" }) {
147 ConstElementPtr const value(parameters->get(key));
148 if (value) {
149 syslog_parameters.emplace(key, value->stringValue());
150 }
151 }
152
153 map = syslog_parameters;
154}
155
156void
158 DatabaseConnection::ParameterMap file_parameters;
159 file_parameters["type"] = "logfile";
160
161 if (!parameters) {
162 map = file_parameters;
163 return;
164 }
165
166 // Strings
167 for (char const* const& key : { "path", "base-name", "time-unit", "prerotate", "postrotate" }) {
168 ConstElementPtr const value(parameters->get(key));
169 if (value) {
170 if (key == std::string("path")) {
171 try {
172 auto valid_path = validatePath(value->stringValue());
173 file_parameters.emplace(key, valid_path);
174 } catch (const SecurityWarn& ex) {
176 .arg(ex.what());
177 file_parameters.emplace(key, value->stringValue());
178 }
179 }
180
181 file_parameters.emplace(key, value->stringValue());
182 }
183 }
184
185 // uint32_t
186 for (char const* const& key : { "count" }) {
187 ConstElementPtr const value(parameters->get(key));
188 if (value) {
189 int64_t integer_value(value->intValue());
190 auto const max(numeric_limits<uint32_t>::max());
191 if (integer_value < 0 || max < integer_value) {
193 key << " value: " << integer_value
194 << " is out of range, expected value: 0.."
195 << max);
196 }
197 file_parameters[key] = boost::lexical_cast<string>(integer_value);
198 }
199 }
200 map = file_parameters;
201}
202
203void
205 if (!parameters) {
206 return;
207 }
208
209 // Strings
210 for (char const* const& key : { "request-parser-format", "response-parser-format", "timestamp-format" }) {
211 ConstElementPtr const value(parameters->get(key));
212 if (value && !value->stringValue().empty()) {
213 map.emplace(key, value->stringValue());
214 }
215 }
216}
217
218struct tm
220 struct tm time_info;
221 struct timespec timestamp = now();
222 localtime_r(&timestamp.tv_sec, &time_info);
223 return (time_info);
224}
225
226struct timespec
227LegalLogMgr::now() const {
228 struct timespec now;
229 clock_gettime(CLOCK_REALTIME, &now);
230 return (now);
231}
232
233string
235 // Get a text representation of the current time.
236 return (getNowString(timestamp_format_));
237}
238
239string
240LegalLogMgr::getNowString(const string& format) const {
241 // Get a text representation of the current time.
242 return (getTimeString(now(), format));
243}
244
245string
246LegalLogMgr::getTimeString(const struct timespec& time, const string& format) {
247 // Get a text representation of the requested time.
248
249 // First a quick and dirty support for fractional seconds: Replace any "%Q"
250 // tokens in the format string with the microsecond count from the timespec,
251 // before handing it off to strftime().
252 string tmp_format = format;
253 for (auto it = tmp_format.begin(); it < tmp_format.end(); ++it) {
254 if (*it == '%' && ((it + 1) < tmp_format.end())) {
255 if (*(it + 1) == 'Q') {
256 // Save the current position.
257 string::size_type pos = it - tmp_format.begin();
258 // Render the microsecond count.
259 ostringstream usec;
260 usec << setw(6) << setfill('0') << (time.tv_nsec / 1000);
261 string microseconds = usec.str();
262 microseconds.insert(3, 1, '.');
263 tmp_format.replace(it, it + 2, microseconds);
264 // Reinitialize the iterator after manipulating the string.
265 it = tmp_format.begin() + pos + microseconds.length() - 1;
266 } else {
267 ++it;
268 }
269 }
270 }
271
272 char buffer[128];
273 struct tm time_info;
274 localtime_r(&time.tv_sec, &time_info);
275
276 if (!strftime(buffer, sizeof(buffer), tmp_format.c_str(), &time_info)) {
278 "strftime returned 0. Maybe the timestamp format '"
279 << tmp_format
280 << "' result is too long, maximum length allowed: "
281 << sizeof(buffer));
282 }
283 return (string(buffer));
284}
285
286string
287LegalLogMgr::genDurationString(const uint32_t secs) {
288 // Because Kea handles lease lifetimes as uint32_t and supports
289 // a value of 0xFFFFFFFF (infinite lifetime), we don't use things like
290 // boost:posix_time::time_duration as they work on longs. Therefore
291 // we'll figure it out ourselves. Besides, the math ain't that hard.
292 if (secs == 0xffffffff) {
293 return ("infinite duration");
294 }
295
296 uint32_t seconds = secs % 60;
297 uint32_t remainder = secs / 60;
298 uint32_t minutes = remainder % 60;
299 remainder /= 60;
300 uint32_t hours = remainder % 24;
301 uint32_t days = remainder / 24;
302
303 ostringstream os;
304 // Only spit out days if we have em.
305 if (days) {
306 os << days << " days ";
307 }
308
309 os << hours << " hrs "
310 << minutes << " mins "
311 << seconds << " secs";
312
313 return (os.str());
314}
315
316string
317LegalLogMgr::vectorHexDump(const vector<uint8_t>& bytes,
318 const string& delimiter) {
319 stringstream tmp;
320 tmp << hex;
321 bool delim = false;
322 for (auto const& it : bytes) {
323 if (delim) {
324 tmp << delimiter;
325 }
326 tmp << setw(2) << setfill('0') << static_cast<unsigned int>(it);
327 delim = true;
328 }
329 return (tmp.str());
330}
331
332string
333LegalLogMgr::vectorDump(const vector<uint8_t>& bytes) {
334 if (bytes.empty()) {
335 return (string());
336 }
337 return (string(bytes.cbegin(), bytes.cend()));
338}
339
340void
341LegalLogMgr::setRequestFormatExpression(const string& extended_format) {
342 Option::Universe universe;
343 if (CfgMgr::instance().getFamily() == AF_INET) {
344 universe = Option::V4;
345 } else {
346 universe = Option::V6;
347 }
348 EvalContext eval_ctx(universe);
349 eval_ctx.parseString(extended_format, EvalContext::PARSER_STRING);
350 request_expression_.reset(new Expression(eval_ctx.expression_));
351}
352
353void
354LegalLogMgr::setResponseFormatExpression(const string& extended_format) {
355 Option::Universe universe;
356 if (CfgMgr::instance().getFamily() == AF_INET) {
357 universe = Option::V4;
358 } else {
359 universe = Option::V6;
360 }
361 EvalContext eval_ctx(universe);
362 eval_ctx.parseString(extended_format, EvalContext::PARSER_STRING);
363 response_expression_.reset(new Expression(eval_ctx.expression_));
364}
365
366void
367LegalLogMgr::setTimestampFormat(const string& timestamp_format) {
368 timestamp_format_ = timestamp_format;
369}
370
371const string
373 switch (action) {
374 case Action::ASSIGN:
375 return ("assigned");
376 case Action::RELEASE:
377 return ("released");
378 default:
379 return ("unknown-action");
380 }
381}
382
383std::string
384LegalLogMgr::getLogPath(bool reset /* = false */, const std::string explicit_path /* = "" */) {
385 if (!legal_log_path_checker_ || reset) {
386 legal_log_path_checker_.reset(new file::PathChecker(LEGAL_LOG_DIR, "KEA_LEGAL_LOG_DIR"));
387 if (!explicit_path.empty()) {
388 legal_log_path_checker_->getPath(true, explicit_path);
389 }
390 }
391
392 return (legal_log_path_checker_->getPath());
393}
394
395std::string
396LegalLogMgr::validatePath(const std::string logpath) {
397 if (!legal_log_path_checker_) {
398 getLogPath();
399 }
400
401 return (legal_log_path_checker_->validateDirectory(logpath));
402}
403
404} // namespace dhcp
405} // 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:222
A generic exception that is thrown if a parameter given violates security check but enforcement is la...
Definition filesystem.h:22
#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:30
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:342
Defines the logger used by the top-level component of kea-lfc.