Coverage Report

Created: 2026-03-31 07:26

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