Coverage Report

Created: 2025-12-08 07:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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