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