/src/kea/src/lib/database/database_connection.cc
Line | Count | Source |
1 | | // Copyright (C) 2015-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 <cc/cfg_to_element.h> |
10 | | #include <cc/default_credentials.h> |
11 | | #include <database/database_connection.h> |
12 | | #include <database/db_exceptions.h> |
13 | | #include <database/db_log.h> |
14 | | #include <database/db_messages.h> |
15 | | #include <exceptions/exceptions.h> |
16 | | #include <util/str.h> |
17 | | |
18 | | #include <boost/algorithm/string.hpp> |
19 | | #include <vector> |
20 | | |
21 | | using namespace isc::asiolink; |
22 | | using namespace isc::data; |
23 | | using namespace isc::util; |
24 | | using namespace std; |
25 | | |
26 | | namespace isc { |
27 | | namespace db { |
28 | | |
29 | | const time_t DatabaseConnection::MAX_DB_TIME = 2147483647; |
30 | | |
31 | | std::string |
32 | 656k | DatabaseConnection::getParameter(const std::string& name) const { |
33 | 656k | ParameterMap::const_iterator param = parameters_.find(name); |
34 | 656k | if (param == parameters_.end()) { |
35 | 555k | isc_throw(BadValue, "Parameter " << name << " not found"); |
36 | 555k | } |
37 | 100k | return (param->second); |
38 | 656k | } |
39 | | |
40 | | DatabaseConnection::ParameterMap |
41 | 40.2k | DatabaseConnection::parse(const std::string& dbaccess) { |
42 | 40.2k | DatabaseConnection::ParameterMap mapped_tokens; |
43 | 40.2k | std::string dba = dbaccess; |
44 | | |
45 | 40.2k | if (!dba.empty()) { |
46 | 40.2k | try { |
47 | 40.2k | vector<string> tokens; |
48 | | |
49 | | // Handle the special case of a password which is enclosed in apostrophes. |
50 | | // Such password may include whitespace. |
51 | 40.2k | std::string password_prefix = "password='"; |
52 | 40.2k | auto password_pos = dba.find(password_prefix); |
53 | 40.2k | if (password_pos != string::npos) { |
54 | | // Password starts with apostrophe, so let's find ending apostrophe. |
55 | 0 | auto password_end_pos = dba.find('\'', password_pos + password_prefix.length()); |
56 | 0 | if (password_end_pos == string::npos) { |
57 | | // No ending apostrophe. This is wrong. |
58 | 0 | isc_throw(InvalidParameter, "Apostrophe (') expected at the end of password"); |
59 | 0 | } |
60 | | // Extract the password value. It starts after the password=' prefix and ends |
61 | | // at the position of ending apostrophe. |
62 | 0 | auto password = dba.substr(password_pos + password_prefix.length(), |
63 | 0 | password_end_pos - password_pos - password_prefix.length()); |
64 | | // Refuse default passwords. |
65 | 0 | DefaultCredentials::check(password); |
66 | 0 | mapped_tokens.insert(make_pair("password", password)); |
67 | | |
68 | | // We need to erase the password from the access string because the generic |
69 | | // algorithm parsing other parameters requires that there are no whitespaces |
70 | | // within the parameter values. |
71 | 0 | dba.erase(password_pos, password_prefix.length() + password.length() + 2); |
72 | | // Leading or trailing whitespace may remain after the password removal. |
73 | 0 | dba = util::str::trim(dba); |
74 | | // If the password was the only parameter in the access string, there is |
75 | | // nothing more to do. |
76 | 0 | if (dba.empty()) { |
77 | 0 | return (mapped_tokens); |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | // We need to pass a string to is_any_of, not just char*. Otherwise |
82 | | // there are cryptic warnings on Debian6 running g++ 4.4 in |
83 | | // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above |
84 | | // array bounds" |
85 | 40.2k | boost::split(tokens, dba, boost::is_any_of(string("\t "))); |
86 | 138k | for (auto const& token : tokens) { |
87 | 138k | size_t pos = token.find("="); |
88 | 138k | if (pos != string::npos) { |
89 | 138k | string name = token.substr(0, pos); |
90 | 138k | string value = token.substr(pos + 1); |
91 | 138k | mapped_tokens.insert(make_pair(name, value)); |
92 | 138k | } else { |
93 | 0 | isc_throw(InvalidParameter, "Cannot parse " << token |
94 | 0 | << ", expected format is name=value"); |
95 | 0 | } |
96 | 138k | } |
97 | 40.2k | } catch (const std::exception& ex) { |
98 | | // We'd obscure the password if we could parse the access string. |
99 | 0 | DB_LOG_ERROR(DB_INVALID_ACCESS).arg(dbaccess); |
100 | 0 | throw; |
101 | 0 | } |
102 | 40.2k | } |
103 | | |
104 | 40.2k | return (mapped_tokens); |
105 | 40.2k | } |
106 | | |
107 | | std::string |
108 | 40.5k | DatabaseConnection::redactedAccessString(const ParameterMap& parameters) { |
109 | | // Reconstruct the access string: start of with an empty string, then |
110 | | // work through all the parameters in the original string and add them. |
111 | 40.5k | std::string access; |
112 | 159k | for (auto const& i : parameters) { |
113 | | |
114 | | // Separate second and subsequent tokens are preceded by a space. |
115 | 159k | if (!access.empty()) { |
116 | 119k | access += " "; |
117 | 119k | } |
118 | | |
119 | | // Append name of parameter... |
120 | 159k | access += i.first; |
121 | 159k | access += "="; |
122 | | |
123 | | // ... and the value, except in the case of the password, where a |
124 | | // redacted value is appended. |
125 | 159k | if (i.first == std::string("password")) { |
126 | 0 | access += "*****"; |
127 | 159k | } else { |
128 | 159k | access += i.second; |
129 | 159k | } |
130 | 159k | } |
131 | | |
132 | 40.5k | return (access); |
133 | 40.5k | } |
134 | | |
135 | | bool |
136 | 0 | DatabaseConnection::configuredReadOnly() const { |
137 | 0 | std::string readonly_value = "false"; |
138 | 0 | try { |
139 | 0 | readonly_value = getParameter("readonly"); |
140 | 0 | boost::algorithm::to_lower(readonly_value); |
141 | 0 | } catch (...) { |
142 | | // Parameter "readonly" hasn't been specified so we simply use |
143 | | // the default value of "false". |
144 | 0 | } |
145 | |
|
146 | 0 | if ((readonly_value != "false") && (readonly_value != "true")) { |
147 | 0 | isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value |
148 | 0 | << "' specified for boolean parameter 'readonly'"); |
149 | 0 | } |
150 | | |
151 | 0 | return (readonly_value == "true"); |
152 | 0 | } |
153 | | |
154 | | void |
155 | 19.6k | DatabaseConnection::makeReconnectCtl(const std::string& timer_name, unsigned int id) { |
156 | 19.6k | string type = "unknown"; |
157 | 19.6k | unsigned int retries = 0; |
158 | 19.6k | unsigned int interval = 0; |
159 | | |
160 | | // Assumes that parsing ensures only valid values are present |
161 | 19.6k | try { |
162 | 19.6k | type = getParameter("type"); |
163 | 19.6k | } catch (...) { |
164 | | // Wasn't specified so we'll use default of "unknown". |
165 | 19.5k | } |
166 | | |
167 | 19.6k | std::string parm_str; |
168 | 19.6k | try { |
169 | 19.6k | parm_str = getParameter("max-reconnect-tries"); |
170 | 19.6k | retries = boost::lexical_cast<unsigned int>(parm_str); |
171 | 19.6k | } catch (...) { |
172 | | // Wasn't specified so we'll use default of 0; |
173 | 19.5k | } |
174 | | |
175 | 19.6k | try { |
176 | 19.6k | parm_str = getParameter("reconnect-wait-time"); |
177 | 19.6k | interval = boost::lexical_cast<unsigned int>(parm_str); |
178 | 19.6k | } catch (...) { |
179 | | // Wasn't specified so we'll use default of 0; |
180 | 19.5k | } |
181 | | |
182 | 19.6k | OnFailAction action = OnFailAction::STOP_RETRY_EXIT; |
183 | 19.6k | try { |
184 | 19.6k | parm_str = getParameter("on-fail"); |
185 | 19.6k | action = ReconnectCtl::onFailActionFromText(parm_str); |
186 | 19.6k | } catch (...) { |
187 | | // Wasn't specified so we'll use default of "stop-retry-exit"; |
188 | 19.5k | } |
189 | | |
190 | 19.6k | reconnect_ctl_ = boost::make_shared<ReconnectCtl>(type, timer_name, retries, |
191 | 19.6k | interval, action, id); |
192 | 19.6k | } |
193 | | |
194 | | bool |
195 | 0 | DatabaseConnection::invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl) { |
196 | 0 | if (DatabaseConnection::db_lost_callback_) { |
197 | 0 | return (DatabaseConnection::db_lost_callback_(db_reconnect_ctl)); |
198 | 0 | } |
199 | | |
200 | 0 | return (false); |
201 | 0 | } |
202 | | |
203 | | bool |
204 | 0 | DatabaseConnection::invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl) { |
205 | 0 | if (DatabaseConnection::db_recovered_callback_) { |
206 | 0 | return (DatabaseConnection::db_recovered_callback_(db_reconnect_ctl)); |
207 | 0 | } |
208 | | |
209 | 0 | return (false); |
210 | 0 | } |
211 | | |
212 | | bool |
213 | 0 | DatabaseConnection::invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl) { |
214 | 0 | if (DatabaseConnection::db_failed_callback_) { |
215 | 0 | return (DatabaseConnection::db_failed_callback_(db_reconnect_ctl)); |
216 | 0 | } |
217 | | |
218 | 0 | return (false); |
219 | 0 | } |
220 | | |
221 | | isc::data::ElementPtr |
222 | 20.0k | DatabaseConnection::toElement(const ParameterMap& params) { |
223 | 20.0k | isc::data::ElementPtr result = isc::data::Element::createMap(); |
224 | | |
225 | 58.4k | for (auto const& param : params) { |
226 | 58.4k | std::string keyword = param.first; |
227 | 58.4k | std::string value = param.second; |
228 | | |
229 | 58.4k | if ((keyword == "lfc-interval") || |
230 | 45.6k | (keyword == "connect-timeout") || |
231 | 45.6k | (keyword == "read-timeout") || |
232 | 45.6k | (keyword == "write-timeout") || |
233 | 45.6k | (keyword == "tcp-user-timeout") || |
234 | 45.6k | (keyword == "reconnect-wait-time") || |
235 | 45.6k | (keyword == "max-reconnect-tries") || |
236 | 45.6k | (keyword == "port") || |
237 | 45.6k | (keyword == "max-row-errors")) { |
238 | | // integer parameters |
239 | 12.8k | int64_t int_value; |
240 | 12.8k | try { |
241 | 12.8k | int_value = boost::lexical_cast<int64_t>(value); |
242 | 12.8k | result->set(keyword, isc::data::Element::create(int_value)); |
243 | 12.8k | } catch (...) { |
244 | 0 | LOG_ERROR(database_logger, DATABASE_TO_JSON_INTEGER_ERROR) |
245 | 0 | .arg(keyword).arg(value); |
246 | 0 | } |
247 | 45.6k | } else if ((keyword == "persist") || |
248 | 39.0k | (keyword == "readonly") || |
249 | 39.0k | (keyword == "retry-on-startup")) { |
250 | 6.58k | if (value == "true") { |
251 | 249 | result->set(keyword, isc::data::Element::create(true)); |
252 | 6.34k | } else if (value == "false") { |
253 | 6.34k | result->set(keyword, isc::data::Element::create(false)); |
254 | 6.34k | } else { |
255 | 0 | LOG_ERROR(database_logger, DATABASE_TO_JSON_BOOLEAN_ERROR) |
256 | 0 | .arg(keyword).arg(value); |
257 | 0 | } |
258 | 39.0k | } else if ((keyword == "type") || |
259 | 19.0k | (keyword == "user") || |
260 | 19.0k | (keyword == "password") || |
261 | 19.0k | (keyword == "host") || |
262 | 19.0k | (keyword == "name") || |
263 | 184 | (keyword == "on-fail") || |
264 | 184 | (keyword == "trust-anchor") || |
265 | 184 | (keyword == "cert-file") || |
266 | 184 | (keyword == "key-file") || |
267 | 184 | (keyword == "ssl-mode") || |
268 | 38.8k | (keyword == "cipher-list")) { |
269 | 38.8k | result->set(keyword, isc::data::Element::create(value)); |
270 | 38.8k | } else { |
271 | 184 | LOG_ERROR(database_logger, DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR) |
272 | 184 | .arg(keyword).arg(value); |
273 | 184 | } |
274 | 58.4k | } |
275 | | |
276 | 20.0k | return (result); |
277 | 20.0k | } |
278 | | |
279 | | isc::data::ElementPtr |
280 | 20.0k | DatabaseConnection::toElementDbAccessString(const std::string& dbaccess) { |
281 | 20.0k | ParameterMap params = parse(dbaccess); |
282 | 20.0k | return (toElement(params)); |
283 | 20.0k | } |
284 | | |
285 | | DbCallback DatabaseConnection::db_lost_callback_ = 0; |
286 | | DbCallback DatabaseConnection::db_recovered_callback_ = 0; |
287 | | DbCallback DatabaseConnection::db_failed_callback_ = 0; |
288 | | bool DatabaseConnection::retry_ = false; |
289 | | IOServicePtr DatabaseConnection::io_service_ = IOServicePtr(); |
290 | | |
291 | | bool DatabaseConnection::test_mode_ = false; |
292 | | |
293 | | } // namespace db |
294 | | } // namespace isc |