/src/kea/src/hooks/dhcp/radius/radius_parsers.cc
Line | Count | Source |
1 | | // Copyright (C) 2018-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 <radius_parsers.h> |
10 | | #include <radius_log.h> |
11 | | #include <cc/data.h> |
12 | | #include <cc/default_credentials.h> |
13 | | #include <dhcpsrv/cfgmgr.h> |
14 | | #include <eval/eval_context.h> |
15 | | #include <util/encode/encode.h> |
16 | | |
17 | | #include <limits> |
18 | | #include <string> |
19 | | #include <cstdlib> |
20 | | #include <cstring> |
21 | | #include <sstream> |
22 | | |
23 | | using namespace std; |
24 | | using namespace isc; |
25 | | using namespace isc::asiolink; |
26 | | using namespace isc::data; |
27 | | using namespace isc::dhcp; |
28 | | using namespace isc::util; |
29 | | |
30 | | namespace isc { |
31 | | namespace radius { |
32 | | |
33 | | /// @brief Keywords for Radius configuration. |
34 | | const set<string> |
35 | | RadiusConfigParser::RADIUS_KEYWORDS = { |
36 | | "access", "accounting", "tls", // services |
37 | | "bindaddr", "canonical-mac-address", "client-id-pop0", |
38 | | "client-id-printable", "deadtime", "dictionary", |
39 | | "extract-duid", "identifier-type4", "identifier-type6", |
40 | | "nas-ports", "protocol", |
41 | | "reselect-subnet-address", "reselect-subnet-pool", |
42 | | "retries", "session-history", "thread-pool-size", "timeout", |
43 | | "use-message-authenticator", |
44 | | "comment" // not saved for toElement |
45 | | }; |
46 | | |
47 | | /// @brief Defaults for Radius configuration. |
48 | | const SimpleDefaults RadiusConfigParser::RADIUS_DEFAULTS = { |
49 | | { "bindaddr", Element::string, "*" }, |
50 | | { "canonical-mac-address", Element::boolean, "false" }, |
51 | | { "client-id-pop0", Element::boolean, "false" }, |
52 | | { "client-id-printable", Element::boolean, "false" }, |
53 | | { "deadtime", Element::integer, "0" }, |
54 | | { "dictionary", Element::string, DICTIONARY }, |
55 | | { "extract-duid", Element::boolean, "true" }, |
56 | | { "identifier-type4", Element::string, "client-id" }, |
57 | | { "identifier-type6", Element::string, "duid" }, |
58 | | { "protocol", Element::string, "UDP" }, |
59 | | { "reselect-subnet-address", Element::boolean, "false" }, |
60 | | { "reselect-subnet-pool", Element::boolean, "false" }, |
61 | | { "retries", Element::integer, "3" }, |
62 | | { "session-history", Element::string, "" }, |
63 | | { "thread-pool-size", Element::integer, "0" }, |
64 | | { "timeout", Element::integer, "10" } |
65 | | }; |
66 | | |
67 | | /// @brief Needed standard attributes. |
68 | | const AttrDefList RadiusConfigParser::USED_STANDARD_ATTR_DEFS = { |
69 | | { PW_USER_NAME, "User-Name", PW_TYPE_STRING }, |
70 | | { PW_USER_PASSWORD, "User-Password", PW_TYPE_STRING }, |
71 | | { PW_NAS_IP_ADDRESS, "NAS-IP-Address", PW_TYPE_IPADDR }, |
72 | | { PW_NAS_PORT, "NAS-Port", PW_TYPE_INTEGER }, |
73 | | { PW_SERVICE_TYPE, "Service-Type", PW_TYPE_INTEGER }, |
74 | | { PW_FRAMED_IP_ADDRESS, "Framed-IP-Address", PW_TYPE_IPADDR }, |
75 | | { PW_REPLY_MESSAGE, "Reply-Message", PW_TYPE_STRING }, |
76 | | { PW_CLASS, "Class", PW_TYPE_STRING }, |
77 | | { PW_VENDOR_SPECIFIC, "Vendor-Specific", PW_TYPE_VSA }, |
78 | | { PW_CALLING_STATION_ID, "Calling-Station-Id", PW_TYPE_STRING }, |
79 | | { PW_ACCT_STATUS_TYPE, "Acct-Status-Type", PW_TYPE_INTEGER }, |
80 | | { PW_ACCT_DELAY_TIME, "Acct-Delay-Time", PW_TYPE_INTEGER }, |
81 | | { PW_ACCT_SESSION_ID, "Acct-Session-Id", PW_TYPE_STRING }, |
82 | | { PW_MESSAGE_AUTHENTICATOR, "Message-Authenticator", PW_TYPE_STRING }, |
83 | | { PW_FRAMED_POOL, "Framed-Pool", PW_TYPE_STRING }, |
84 | | { PW_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", PW_TYPE_IPV6ADDR }, |
85 | | { PW_DELEGATED_IPV6_PREFIX, "Delegated-IPv6-Prefix", PW_TYPE_IPV6PREFIX }, |
86 | | { PW_FRAMED_IPV6_ADDRESS, "Framed-IPv6-Address", PW_TYPE_IPV6ADDR } |
87 | | }; |
88 | | |
89 | | /// @brief Defaults for Radius attribute configuration. |
90 | | const SimpleDefaults RadiusAttributeParser::ATTRIBUTE_DEFAULTS = { |
91 | | { "data", Element::string, "" }, |
92 | | { "expr", Element::string, "" }, |
93 | | { "raw", Element::string, "" }, |
94 | | { "vendor", Element::string, "" } |
95 | | }; |
96 | | |
97 | | void |
98 | 0 | RadiusConfigParser::parse(ElementPtr& config) { |
99 | 0 | try { |
100 | 0 | RadiusImpl& riref = RadiusImpl::instance(); |
101 | | |
102 | | // Set defaults. |
103 | 0 | setDefaults(config, RADIUS_DEFAULTS); |
104 | | |
105 | | // dictionary (do it first). |
106 | 0 | const ConstElementPtr& dictionary = config->get("dictionary"); |
107 | 0 | riref.dictionary_ = dictionary->stringValue(); |
108 | | |
109 | | // Read the dictionary |
110 | 0 | if (!AttrDefs::instance().getByType(1)) { |
111 | 0 | uint32_t vendor = 0; |
112 | 0 | try { |
113 | 0 | AttrDefs::instance().readDictionary(riref.dictionary_, vendor); |
114 | 0 | } catch (const exception& ex) { |
115 | 0 | isc_throw(BadValue, "can't read radius dictionary: " |
116 | 0 | << ex.what()); |
117 | 0 | } |
118 | 0 | if (vendor != 0) { |
119 | 0 | isc_throw(BadValue, "vendor definitions were not properly " |
120 | 0 | << "closed: vendor " << vendor << " is still open"); |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | // Check it. |
125 | 0 | AttrDefs::instance().checkStandardDefs(USED_STANDARD_ATTR_DEFS); |
126 | | |
127 | | // Protocol. |
128 | 0 | const ConstElementPtr& protocol = config->get("protocol"); |
129 | 0 | string proto = protocol->stringValue(); |
130 | 0 | if (proto == "UDP") { |
131 | 0 | riref.proto_ = PW_PROTO_UDP; |
132 | 0 | } else if (proto == "TCP") { |
133 | 0 | riref.proto_ = PW_PROTO_TCP; |
134 | 0 | } else if (proto == "TLS") { |
135 | 0 | riref.proto_ = PW_PROTO_TLS; |
136 | 0 | } else { |
137 | 0 | isc_throw(BadValue, "unknown protocol " << proto); |
138 | 0 | } |
139 | 0 | if (riref.proto_ == PW_PROTO_TCP) { |
140 | 0 | isc_throw(NotImplemented, "protocol 'TCP' is not supported"); |
141 | 0 | } |
142 | | |
143 | | // bindaddr. |
144 | 0 | const ConstElementPtr& bindaddr = config->get("bindaddr"); |
145 | 0 | riref.bindaddr_ = bindaddr->stringValue(); |
146 | | |
147 | | // canonical-mac-address. |
148 | 0 | const ConstElementPtr& canonical = config->get("canonical-mac-address"); |
149 | 0 | riref.canonical_mac_address_ = canonical->boolValue(); |
150 | | |
151 | | // client-id-pop0. |
152 | 0 | const ConstElementPtr& pop0 = config->get("client-id-pop0"); |
153 | 0 | riref.clientid_pop0_ = pop0->boolValue(); |
154 | | |
155 | | // client-id-printable. |
156 | 0 | const ConstElementPtr& try_printable = config->get("client-id-printable"); |
157 | 0 | riref.clientid_printable_ = try_printable->boolValue(); |
158 | | |
159 | | // deadtime. |
160 | 0 | const ConstElementPtr& deadtime = config->get("deadtime"); |
161 | 0 | int64_t deadtime64 = deadtime->intValue(); |
162 | 0 | if ((deadtime64 < 0) || |
163 | 0 | (deadtime64 > numeric_limits<unsigned>::max())) { |
164 | 0 | isc_throw(OutOfRange, "bad deadtime " << deadtime64 |
165 | 0 | << " not in [0.." |
166 | 0 | << numeric_limits<unsigned>::max() << "]"); |
167 | 0 | } |
168 | 0 | riref.deadtime_ = static_cast<unsigned>(deadtime64); |
169 | | |
170 | | // extract-duid. |
171 | 0 | const ConstElementPtr& rfc4361 = config->get("extract-duid"); |
172 | 0 | riref.extract_duid_ = rfc4361->boolValue(); |
173 | | |
174 | | // identifier-type4. |
175 | 0 | const ConstElementPtr& id_type4 = config->get("identifier-type4"); |
176 | 0 | riref.id_type4_ = Host::getIdentifierType(id_type4->stringValue()); |
177 | | |
178 | | // identifier-type6. |
179 | 0 | const ConstElementPtr& id_type6 = config->get("identifier-type6"); |
180 | 0 | riref.id_type6_ = Host::getIdentifierType(id_type6->stringValue()); |
181 | | |
182 | | // reselect-subnet-address. |
183 | 0 | const ConstElementPtr& resel_addr = |
184 | 0 | config->get("reselect-subnet-address"); |
185 | 0 | riref.reselect_subnet_address_ = resel_addr->boolValue(); |
186 | | |
187 | | // reselect-subnet-pool. |
188 | 0 | const ConstElementPtr& resel_pool = |
189 | 0 | config->get("reselect-subnet-pool"); |
190 | 0 | riref.reselect_subnet_pool_ = resel_pool->boolValue(); |
191 | | |
192 | | // retries. |
193 | 0 | const ConstElementPtr& retries = config->get("retries"); |
194 | 0 | int64_t retries64 = retries->intValue(); |
195 | 0 | if ((retries64 < 0) || |
196 | 0 | (retries64 > numeric_limits<unsigned>::max())) { |
197 | 0 | isc_throw(OutOfRange, "bad retries " << retries64 |
198 | 0 | << " not in [0.." |
199 | 0 | << numeric_limits<unsigned>::max() << "]"); |
200 | 0 | } |
201 | 0 | riref.retries_ = static_cast<unsigned>(retries64); |
202 | | |
203 | | // session-history. |
204 | 0 | const ConstElementPtr& session_history = config->get("session-history"); |
205 | 0 | riref.session_history_filename_ = session_history->stringValue(); |
206 | | |
207 | | // thread-pool-size. |
208 | 0 | const ConstElementPtr& thread_pool_size = config->get("thread-pool-size"); |
209 | 0 | riref.thread_pool_size_ = thread_pool_size->intValue(); |
210 | | |
211 | | // timeout. |
212 | 0 | const ConstElementPtr& timeout = config->get("timeout"); |
213 | 0 | int64_t timeout64 = timeout->intValue(); |
214 | 0 | if ((timeout64 < 0) || |
215 | 0 | (timeout64 > numeric_limits<long>::max() / 1000)) { |
216 | 0 | isc_throw(OutOfRange, "bad timeout " << timeout64 |
217 | 0 | << " not in [0.." |
218 | 0 | << (numeric_limits<long>::max() / 1000) << "]"); |
219 | 0 | } |
220 | 0 | riref.timeout_ = static_cast<unsigned>(timeout64); |
221 | | |
222 | | // use-message-authenticator. |
223 | 0 | const ConstElementPtr use_ma = config->get("use-message-authenticator"); |
224 | 0 | if (!use_ma) { |
225 | 0 | if (riref.proto_ != PW_PROTO_TLS) { |
226 | 0 | riref.use_message_authenticator_ = true; |
227 | 0 | } else { |
228 | 0 | riref.use_message_authenticator_ = false; |
229 | 0 | } |
230 | 0 | } else { |
231 | 0 | riref.use_message_authenticator_ = use_ma->boolValue(); |
232 | 0 | } |
233 | | |
234 | | // TLS service. |
235 | 0 | const ConstElementPtr& tls = config->get("tls"); |
236 | 0 | if (tls) { |
237 | 0 | if (riref.proto_ != PW_PROTO_TLS) { |
238 | 0 | isc_throw(BadValue, "'tls' service can't be configured " |
239 | 0 | << "when protocol is not 'TLS'"); |
240 | 0 | } |
241 | 0 | RadiusServiceParser parser; |
242 | 0 | parser.parse(riref.tls_, tls); |
243 | 0 | parser.checkAttributes(riref.tls_); |
244 | 0 | } |
245 | | |
246 | | // Access service. |
247 | 0 | const ConstElementPtr& access = config->get("access"); |
248 | 0 | if (access) { |
249 | 0 | RadiusServiceParser parser; |
250 | 0 | parser.parse(riref.auth_, access); |
251 | 0 | parser.checkAttributes(riref.auth_); |
252 | 0 | } |
253 | | |
254 | | // Accounting service. |
255 | 0 | const ConstElementPtr& accounting = config->get("accounting"); |
256 | 0 | if (accounting) { |
257 | 0 | RadiusServiceParser parser; |
258 | 0 | parser.parse(riref.acct_, accounting); |
259 | 0 | parser.checkAttributes(riref.acct_); |
260 | 0 | } |
261 | | |
262 | | // nas-ports (last so we can return when it is not present. |
263 | 0 | const ConstElementPtr& nas_ports = config->get("nas-ports"); |
264 | 0 | if (!nas_ports) { |
265 | 0 | return; |
266 | 0 | } |
267 | 0 | for (auto const& entry : nas_ports->listValue()) { |
268 | | // port is mandatory. |
269 | 0 | const ConstElementPtr& port = entry->get("port"); |
270 | 0 | if (!port) { |
271 | 0 | isc_throw(BadValue, "missing port in nas-ports entry: " |
272 | 0 | << entry->str()); |
273 | 0 | } |
274 | | |
275 | | // By subnet-id. |
276 | 0 | const ConstElementPtr& id = entry->get("subnet-id"); |
277 | 0 | if (id) { |
278 | 0 | riref.remap_[id->intValue()] = port->intValue(); |
279 | 0 | continue; |
280 | 0 | } |
281 | | |
282 | | // By subnet-prefix (to be resolved into an ID). |
283 | 0 | const ConstElementPtr& prefix = entry->get("subnet-prefix"); |
284 | 0 | if (prefix) { |
285 | 0 | if (CfgMgr::instance().getFamily() == AF_INET) { |
286 | 0 | auto subnet = CfgMgr::instance().getStagingCfg()-> |
287 | 0 | getCfgSubnets4()->getByPrefix(prefix->stringValue()); |
288 | 0 | if (!subnet) { |
289 | 0 | isc_throw(BadValue, "can't find subnet for " |
290 | 0 | << entry->str()); |
291 | 0 | } |
292 | 0 | riref.remap_[subnet->getID()] = port->intValue(); |
293 | 0 | continue; |
294 | 0 | } else { |
295 | 0 | auto subnet = CfgMgr::instance().getStagingCfg()-> |
296 | 0 | getCfgSubnets6()->getByPrefix(prefix->stringValue()); |
297 | 0 | if (!subnet) { |
298 | 0 | isc_throw(BadValue, "can't find subnet for " |
299 | 0 | << entry->str()); |
300 | 0 | } |
301 | 0 | riref.remap_[subnet->getID()] = port->intValue(); |
302 | 0 | continue; |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | // By shared-network-name (to be resolved, add all subnets). |
307 | 0 | const ConstElementPtr& name = entry->get("shared-network-name"); |
308 | 0 | if (name) { |
309 | 0 | if (CfgMgr::instance().getFamily() == AF_INET) { |
310 | 0 | auto network = CfgMgr::instance().getStagingCfg()-> |
311 | 0 | getCfgSharedNetworks4()->getByName(name->stringValue()); |
312 | 0 | if (!network) { |
313 | 0 | isc_throw(BadValue, "can't find shared network for " |
314 | 0 | << entry->str()); |
315 | 0 | } |
316 | 0 | for (auto const& subnet : *network->getAllSubnets()) { |
317 | 0 | riref.remap_[subnet->getID()] = port->intValue(); |
318 | 0 | } |
319 | 0 | continue; |
320 | 0 | } else { |
321 | 0 | auto network = CfgMgr::instance().getStagingCfg()-> |
322 | 0 | getCfgSharedNetworks6()->getByName(name->stringValue()); |
323 | 0 | if (!network) { |
324 | 0 | isc_throw(BadValue, "can't find shared network for " |
325 | 0 | << entry->str()); |
326 | 0 | } |
327 | 0 | for (auto const& subnet : *network->getAllSubnets()) { |
328 | 0 | riref.remap_[subnet->getID()] = port->intValue(); |
329 | 0 | } |
330 | 0 | continue; |
331 | 0 | } |
332 | 0 | } |
333 | | |
334 | | // Unknown selector. |
335 | 0 | if (entry->size() > 1) { |
336 | 0 | isc_throw(BadValue, "unknown selector in " << entry->str()); |
337 | 0 | } |
338 | | |
339 | | // Default is in subnet 0 (SUBNET_ID_DEFAULT). |
340 | 0 | riref.remap_[SUBNET_ID_DEFAULT] = port->intValue(); |
341 | 0 | } |
342 | |
|
343 | 0 | } catch (const ConfigError&) { |
344 | 0 | throw; |
345 | 0 | } catch (const std::exception& ex) { |
346 | 0 | isc_throw(ConfigError, ex.what()); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | /// @brief Keywords for service configuration. |
351 | | const set<string> |
352 | | RadiusServiceParser::SERVICE_KEYWORDS = { |
353 | | "enabled", "servers", "attributes", "peer-updates", "max-pending-requests", |
354 | | "idle-timer-interval" |
355 | | }; |
356 | | |
357 | | void |
358 | | RadiusServiceParser::parse(const RadiusServicePtr& service, |
359 | 0 | const ConstElementPtr& srv_cfg) { |
360 | 0 | try { |
361 | 0 | RadiusImpl& riref = RadiusImpl::instance(); |
362 | | |
363 | | // map type. |
364 | 0 | if (srv_cfg->getType() != Element::map) { |
365 | 0 | isc_throw(BadValue, "expected service to be map, but got " |
366 | 0 | << Element::typeToName(srv_cfg->getType()) |
367 | 0 | << " instead"); |
368 | 0 | } |
369 | | |
370 | | // keywords. |
371 | 0 | const set<string> keywords = RadiusServiceParser::SERVICE_KEYWORDS; |
372 | 0 | for (auto const& entry : srv_cfg->mapValue()) { |
373 | 0 | if (keywords.count(entry.first) == 0) { |
374 | 0 | isc_throw(BadValue, "unknown service parameter: " |
375 | 0 | << entry.first); |
376 | 0 | } |
377 | 0 | } |
378 | | |
379 | | // Enabled. |
380 | 0 | const ConstElementPtr& enabled = srv_cfg->get("enabled"); |
381 | 0 | if (enabled) { |
382 | 0 | if (riref.proto_ != PW_PROTO_TLS) { |
383 | 0 | isc_throw(BadValue, "'enabled' makes sense only with TLS"); |
384 | 0 | } |
385 | 0 | if (enabled->getType() != Element::boolean) { |
386 | 0 | isc_throw(BadValue, "expected enabled to be boolean, " |
387 | 0 | << "but got " |
388 | 0 | << Element::typeToName(enabled->getType()) |
389 | 0 | << " instead"); |
390 | 0 | } |
391 | 0 | if (service->name_ == "tls") { |
392 | 0 | isc_throw(BadValue, "can't set enabled in 'tls'"); |
393 | 0 | } |
394 | 0 | service->enabled_ = enabled->boolValue(); |
395 | 0 | } else { |
396 | 0 | if ((riref.proto_ == PW_PROTO_TLS) && |
397 | 0 | (service->name_ != "tls")) { |
398 | 0 | service->enabled_ = true; |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | // servers. |
403 | 0 | const ConstElementPtr& servers = srv_cfg->get("servers"); |
404 | 0 | if (servers) { |
405 | 0 | if ((riref.proto_ == PW_PROTO_TLS) && |
406 | 0 | (service->name_ != "tls")) { |
407 | 0 | isc_throw(BadValue, "can't have servers entry in '" |
408 | 0 | << service->name_ << "' with TLS"); |
409 | 0 | } |
410 | 0 | RadiusServerListParser parser; |
411 | 0 | parser.parse(service, servers); |
412 | 0 | if (!service->servers_.empty()) { |
413 | 0 | service->enabled_ = true; |
414 | 0 | } |
415 | 0 | } |
416 | | |
417 | | // attributes. |
418 | 0 | const ConstElementPtr& attributes = srv_cfg->get("attributes"); |
419 | 0 | if (attributes) { |
420 | 0 | if (service->name_ == "tls") { |
421 | 0 | isc_throw(BadValue, "can't define attributes in 'tls'"); |
422 | 0 | } |
423 | 0 | RadiusAttributeListParser parser; |
424 | 0 | parser.parse(service, attributes); |
425 | 0 | } |
426 | | |
427 | | // peer-updates. |
428 | 0 | const ConstElementPtr& peer_updates = srv_cfg->get("peer-updates"); |
429 | 0 | if (peer_updates) { |
430 | 0 | if (service->name_ != "accounting") { |
431 | 0 | isc_throw(BadValue, "peer-updates configured for the " |
432 | 0 | << service->name_ << " service, but it is " |
433 | 0 | << "only supported for the accounting service"); |
434 | 0 | } |
435 | 0 | if (peer_updates->getType() != Element::boolean) { |
436 | 0 | isc_throw(BadValue, "expected peer-updates to be boolean, " |
437 | 0 | << "but got " |
438 | 0 | << Element::typeToName(peer_updates->getType()) |
439 | 0 | << " instead"); |
440 | 0 | } |
441 | 0 | service->peer_updates_ = peer_updates->boolValue(); |
442 | 0 | } |
443 | | |
444 | | // max-pending-requests. |
445 | 0 | const ConstElementPtr& max_pending_requests = |
446 | 0 | srv_cfg->get("max-pending-requests"); |
447 | 0 | if (max_pending_requests) { |
448 | 0 | if (service->name_ != "access") { |
449 | 0 | isc_throw(BadValue, "max-pending-requests configured for the " |
450 | 0 | << service->name_ << " service, but it is only " |
451 | 0 | << "supported for the access service"); |
452 | 0 | } |
453 | 0 | if (max_pending_requests->getType() != Element::integer) { |
454 | 0 | isc_throw(BadValue, "expected max-pending-requests to be " |
455 | 0 | << "integer, but got " |
456 | 0 | << Element::typeToName(max_pending_requests->getType()) |
457 | 0 | << " instead"); |
458 | 0 | } |
459 | 0 | if (max_pending_requests->intValue() < 0) { |
460 | 0 | isc_throw(BadValue, "expected max-pending-requests to be " |
461 | 0 | << "positive, but got " |
462 | 0 | << max_pending_requests->intValue() |
463 | 0 | << " instead"); |
464 | 0 | } |
465 | 0 | service->max_pending_requests_ = max_pending_requests->intValue(); |
466 | 0 | } |
467 | | |
468 | | // idle-timer-interval. |
469 | 0 | const ConstElementPtr& idle_timer_interval = |
470 | 0 | srv_cfg->get("idle-timer-interval"); |
471 | 0 | if (idle_timer_interval) { |
472 | 0 | if ((riref.proto_ == PW_PROTO_TLS) && |
473 | 0 | (service->name_ != "tls")) { |
474 | 0 | isc_throw(BadValue, "can't have idle-timer-interval entry in '" |
475 | 0 | << service->name_ << "' with TLS"); |
476 | 0 | } |
477 | 0 | if (idle_timer_interval->getType() != Element::integer) { |
478 | 0 | isc_throw(BadValue, "expected idle-timer-interval to be " |
479 | 0 | << "integer, but got " |
480 | 0 | << Element::typeToName(idle_timer_interval->getType()) |
481 | 0 | << " instead"); |
482 | 0 | } |
483 | 0 | if (idle_timer_interval->intValue() < 0) { |
484 | 0 | isc_throw(BadValue, "expected idle-timer-interval to be " |
485 | 0 | << "positive, but got " |
486 | 0 | << idle_timer_interval->intValue() |
487 | 0 | << " instead"); |
488 | 0 | } |
489 | 0 | service->idle_timer_interval_ = idle_timer_interval->intValue(); |
490 | 0 | } |
491 | 0 | } catch (const std::exception& ex) { |
492 | 0 | isc_throw(ConfigError, ex.what() << " (parsing " |
493 | 0 | << service->name_ << ")"); |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | | void |
498 | 0 | RadiusServiceParser::checkAttributes(const RadiusServicePtr& service) { |
499 | 0 | if (!service->enabled_) { |
500 | 0 | return; |
501 | 0 | } |
502 | | |
503 | 0 | const CfgAttributes& cfg_attrs = service->attributes_; |
504 | 0 | const Attributes& attrs = cfg_attrs.getAll(); |
505 | 0 | if (service->name_ == "access") { |
506 | | // Nothing yet. |
507 | 0 | } else if (service->name_ == "accounting") { |
508 | | // Expressions have no associated attributes. |
509 | 0 | if (cfg_attrs.size() > attrs.size()) { |
510 | 0 | isc_throw(ConfigError, |
511 | 0 | "Expressions are not yet supported in accounting"); |
512 | 0 | } |
513 | 0 | } |
514 | 0 | } |
515 | | |
516 | | void |
517 | | RadiusServerListParser::parse(const RadiusServicePtr& service, |
518 | 0 | const ConstElementPtr& srv_list) { |
519 | 0 | for (auto const& srv : srv_list->listValue()) { |
520 | 0 | RadiusServerParser parser; |
521 | 0 | parser.parse(service, srv); |
522 | 0 | } |
523 | 0 | } |
524 | | |
525 | | void |
526 | | RadiusServerParser::parse(const RadiusServicePtr& service, |
527 | 0 | const ElementPtr& server) { |
528 | 0 | RadiusImpl& riref = RadiusImpl::instance(); |
529 | | |
530 | | // Details will be logged. |
531 | 0 | ostringstream msg; |
532 | | |
533 | | // Peer address (was name). |
534 | 0 | IOAddress peer_addr("::"); |
535 | 0 | const string& name = getString(server, "name"); |
536 | 0 | try { |
537 | 0 | peer_addr = IOAddress(name); |
538 | 0 | } catch (const Exception&) { |
539 | 0 | try { |
540 | 0 | peer_addr = Server::getAddress(name); |
541 | 0 | } catch (const Exception& ex) { |
542 | 0 | isc_throw(ConfigError, "can't resolve '" << name << "': " |
543 | 0 | << ex.what()); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | msg << "peer-addr=" << peer_addr.toText(); |
547 | | |
548 | | // port. |
549 | 0 | uint16_t port; |
550 | 0 | if (server->contains("port")) { |
551 | 0 | port = getUint16(server, "port"); |
552 | 0 | } else if (service->name_ == "tls") { |
553 | 0 | port = PW_TLS_PORT; |
554 | 0 | } else if (service->name_ == "access") { |
555 | 0 | port = PW_AUTH_PORT; |
556 | 0 | } else { |
557 | 0 | port = PW_ACCT_PORT; |
558 | 0 | } |
559 | 0 | msg << " port=" << port; |
560 | | |
561 | | // Local address. |
562 | 0 | IOAddress local_addr("::"); |
563 | 0 | const string& local = riref.bindaddr_; |
564 | 0 | if (local != "*") { |
565 | 0 | try { |
566 | 0 | local_addr = IOAddress(local); |
567 | 0 | } catch (const Exception& ex) { |
568 | 0 | isc_throw(ConfigError, "bad local address '" << local << "': " |
569 | 0 | << ex.what()); |
570 | 0 | } |
571 | 0 | } else { |
572 | 0 | try { |
573 | 0 | local_addr = Server::getSrcAddress(peer_addr); |
574 | 0 | } catch (const Exception& ex) { |
575 | 0 | isc_throw(ConfigError, "can't get local address: " << ex.what()); |
576 | 0 | } |
577 | 0 | } |
578 | 0 | msg << " local_addr=" << local_addr; |
579 | | |
580 | | // secret. |
581 | 0 | string secret; |
582 | 0 | if (!server->contains("secret") && (service->name_ == "tls")) { |
583 | 0 | secret = "radsec"; |
584 | 0 | } else { |
585 | 0 | secret = getString(server, "secret"); |
586 | 0 | try { |
587 | 0 | DefaultCredentials::check(secret); |
588 | 0 | } catch (const DefaultCredential& ex) { |
589 | 0 | isc_throw(ConfigError, "illegal use of a default secret"); |
590 | 0 | } |
591 | 0 | } |
592 | 0 | msg << " secret=*****"; |
593 | | |
594 | | // TLS parameters. |
595 | 0 | TlsContextPtr tls_context; |
596 | 0 | if (service->name_ == "tls") { |
597 | 0 | string trust_anchor = getString(server, "trust-anchor"); |
598 | 0 | string cert_file = getString(server, "cert-file"); |
599 | 0 | string key_file = getString(server, "key-file"); |
600 | 0 | TlsContext::configure(tls_context, TlsRole::CLIENT, |
601 | 0 | trust_anchor, cert_file, key_file); |
602 | 0 | } |
603 | |
|
604 | 0 | try { |
605 | 0 | ServerPtr srv(new Server(peer_addr, port, local_addr, tls_context, |
606 | 0 | secret, riref.timeout_, riref.deadtime_)); |
607 | 0 | service->servers_.push_back(srv); |
608 | 0 | } catch (const Exception& ex) { |
609 | 0 | isc_throw(ConfigError, "can't create " << service->name_ |
610 | 0 | << " server '" << msg.str() << "': " << ex.what()); |
611 | 0 | } |
612 | | |
613 | | // Done. |
614 | 0 | LOG_INFO(radius_logger, RADIUS_SERVER_CONFIGURED) |
615 | 0 | .arg(service->name_) |
616 | 0 | .arg(msg.str()); |
617 | 0 | } |
618 | | |
619 | | void |
620 | | RadiusAttributeListParser::parse(const RadiusServicePtr& service, |
621 | 0 | const ConstElementPtr& attr_list) { |
622 | 0 | for (auto const& attr : attr_list->listValue()) { |
623 | 0 | RadiusAttributeParser parser; |
624 | 0 | parser.parse(service, attr); |
625 | 0 | } |
626 | 0 | } |
627 | | |
628 | | void |
629 | | RadiusAttributeParser::parse(const RadiusServicePtr& service, |
630 | 0 | const ElementPtr& attr) { |
631 | 0 | AttrDefPtr def; |
632 | | |
633 | | // Set defaults. |
634 | 0 | setDefaults(attr, ATTRIBUTE_DEFAULTS); |
635 | | |
636 | | // vendor. |
637 | 0 | uint32_t vendor = 0; |
638 | 0 | const ConstElementPtr& vendor_elem = attr->get("vendor"); |
639 | 0 | if (!vendor_elem) { |
640 | | // Should not happen as it is added by setDefaults. |
641 | 0 | isc_throw(Unexpected, "no vendor parameter"); |
642 | 0 | } else if (vendor_elem->getType() != Element::string) { |
643 | | // Expected to be a common error. |
644 | 0 | isc_throw(TypeError, "vendor parameter must be a string"); |
645 | 0 | } |
646 | 0 | const string& vendor_txt = vendor_elem->stringValue(); |
647 | 0 | if (!vendor_txt.empty()) { |
648 | 0 | IntCstDefPtr vendor_cst = |
649 | 0 | AttrDefs::instance().getByName(PW_VENDOR_SPECIFIC, vendor_txt); |
650 | 0 | if (vendor_cst) { |
651 | 0 | vendor = vendor_cst->value_; |
652 | 0 | } else { |
653 | 0 | try { |
654 | 0 | int64_t val = boost::lexical_cast<int64_t>(vendor_txt); |
655 | 0 | if ((val < numeric_limits<int32_t>::min()) || |
656 | 0 | (val > numeric_limits<uint32_t>::max())) { |
657 | 0 | isc_throw(Unexpected, "not 32 bit " << vendor_txt); |
658 | 0 | } |
659 | 0 | vendor = static_cast<uint32_t>(val); |
660 | 0 | } catch (...) { |
661 | 0 | isc_throw(ConfigError, "can't parse vendor '" |
662 | 0 | << vendor_txt << "'"); |
663 | 0 | } |
664 | 0 | } |
665 | 0 | } |
666 | | |
667 | | // name. |
668 | 0 | const ConstElementPtr& name = attr->get("name"); |
669 | 0 | if (name) { |
670 | 0 | if (name->stringValue().empty()) { |
671 | 0 | isc_throw(ConfigError, "attribute name is empty"); |
672 | 0 | } |
673 | 0 | def = AttrDefs::instance().getByName(name->stringValue(), vendor); |
674 | 0 | if (!def) { |
675 | 0 | ostringstream msg; |
676 | 0 | msg << "attribute '" << name->stringValue() << "'"; |
677 | 0 | if (vendor != 0) { |
678 | 0 | msg << " in vendor '" << vendor_txt << "'"; |
679 | 0 | } |
680 | 0 | msg << " is unknown"; |
681 | 0 | isc_throw(ConfigError, msg.str()); |
682 | 0 | } |
683 | 0 | } |
684 | | |
685 | | // type. |
686 | 0 | const ConstElementPtr& type = attr->get("type"); |
687 | 0 | if (type) { |
688 | 0 | if ((type->intValue() < 0) || (type->intValue() > 255)) { |
689 | 0 | isc_throw(ConfigError, "out of range attribute type " |
690 | 0 | << type->intValue()); |
691 | 0 | } |
692 | 0 | uint8_t attrib = static_cast<uint8_t>(type->intValue()); |
693 | 0 | if (def && (def->type_ != attrib)) { |
694 | 0 | ostringstream msg; |
695 | 0 | msg << "'" << name->stringValue() << "' attribute"; |
696 | 0 | if (vendor != 0) { |
697 | 0 | msg << " in vendor '" << vendor_txt << "'"; |
698 | 0 | } |
699 | 0 | msg << " has type " << static_cast<unsigned>(def->type_) |
700 | 0 | << ", not " << static_cast<unsigned>(attrib); |
701 | 0 | isc_throw(ConfigError, msg.str()); |
702 | 0 | } |
703 | 0 | if (!def) { |
704 | 0 | def = AttrDefs::instance().getByType(attrib, vendor); |
705 | 0 | } |
706 | 0 | if (!def) { |
707 | 0 | ostringstream msg; |
708 | 0 | msg << "attribute type " << static_cast<unsigned>(attrib); |
709 | 0 | if (vendor != 0) { |
710 | 0 | msg << " in vendor '" << vendor_txt << "'"; |
711 | 0 | } |
712 | 0 | msg << " is unknown"; |
713 | 0 | isc_throw(ConfigError, msg.str()); |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | | // name or type are required. |
718 | 0 | if (!def) { |
719 | 0 | isc_throw(ConfigError, "name or type are required"); |
720 | 0 | } |
721 | | |
722 | | // data. |
723 | 0 | const string& data_txt = getString(attr, "data"); |
724 | | |
725 | | // raw. |
726 | 0 | const string& raw_txt = getString(attr, "raw"); |
727 | | |
728 | | // expr. |
729 | 0 | const string& expr_txt = getString(attr, "expr"); |
730 | | |
731 | | /// @todo: raw. |
732 | |
|
733 | 0 | ExpressionPtr expression; |
734 | 0 | if (!expr_txt.empty()) { |
735 | 0 | if (!data_txt.empty() || !raw_txt.empty()) { |
736 | 0 | isc_throw(ConfigError, "data, raw and expr are exclusive"); |
737 | 0 | } |
738 | 0 | Option::Universe universe; |
739 | 0 | if (CfgMgr::instance().getFamily() == AF_INET) { |
740 | 0 | universe = Option::V4; |
741 | 0 | } else { |
742 | 0 | universe = Option::V6; |
743 | 0 | } |
744 | 0 | try { |
745 | 0 | EvalContext eval_ctx(universe); |
746 | 0 | eval_ctx.parseString(expr_txt, EvalContext::PARSER_STRING); |
747 | 0 | expression.reset(new Expression()); |
748 | 0 | *expression = eval_ctx.expression_; |
749 | 0 | } catch (const std::exception& ex) { |
750 | 0 | isc_throw(ConfigError, "expression: [" << expr_txt |
751 | 0 | << "] error: " << ex.what() << " for " |
752 | 0 | << def->name_ << " attribute"); |
753 | 0 | } |
754 | | |
755 | 0 | service->attributes_.add(def, AttributePtr(), expression, expr_txt); |
756 | 0 | } else if (!raw_txt.empty()) { |
757 | 0 | if (!data_txt.empty()) { |
758 | 0 | isc_throw(ConfigError, "data and raw are exclusive"); |
759 | 0 | } |
760 | | // The decodeHex function expects that the string contains an |
761 | | // even number of digits. If we don't meet this requirement, |
762 | | // we have to insert a leading 0. |
763 | 0 | string padded = raw_txt; |
764 | 0 | if ((padded.size() % 2) != 0) { |
765 | 0 | padded = padded.insert(0, "0"); |
766 | 0 | } |
767 | 0 | vector<uint8_t> binary; |
768 | 0 | try { |
769 | 0 | encode::decodeHex(padded, binary); |
770 | 0 | } catch (...) { |
771 | 0 | isc_throw(ConfigError, "can't decode raw: [" << raw_txt |
772 | 0 | << "] for " << def->name_ << " attribute"); |
773 | 0 | } |
774 | 0 | try { |
775 | 0 | AttributePtr attribute = Attribute::fromBytes(def, binary); |
776 | 0 | service->attributes_.add(def, attribute); |
777 | 0 | } catch (const Exception& ex) { |
778 | 0 | isc_throw(ConfigError, "can't create " << def->name_ |
779 | 0 | << " attribute from raw: [" << raw_txt << "]: " |
780 | 0 | << ex.what()); |
781 | 0 | } |
782 | 0 | } else { |
783 | 0 | try { |
784 | 0 | AttributePtr attribute = Attribute::fromText(def, data_txt); |
785 | 0 | service->attributes_.add(def, attribute); |
786 | 0 | } catch (const Exception& ex) { |
787 | 0 | isc_throw(ConfigError, "can't create " << def->name_ |
788 | 0 | << " attribute from [" << data_txt << "]: " |
789 | 0 | << ex.what()); |
790 | 0 | } |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | | } // end of namespace isc::radius |
795 | | } // end of namespace isc |