VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/Dhcpd/Config.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.7 KB
Line 
1/* $Id: Config.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * DHCP server - server configuration
4 */
5
6/*
7 * Copyright (C) 2017-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include "DhcpdInternal.h"
33
34#include <iprt/ctype.h>
35#include <iprt/net.h> /* NB: must come before getopt.h */
36#include <iprt/getopt.h>
37#include <iprt/path.h>
38#include <iprt/message.h>
39#include <iprt/string.h>
40#include <iprt/uuid.h>
41#include <iprt/cpp/path.h>
42
43#include <VBox/com/utils.h> /* For log initialization. */
44
45#include "Config.h"
46
47
48/*********************************************************************************************************************************
49* Global Variables *
50*********************************************************************************************************************************/
51/*static*/ bool Config::g_fInitializedLog = false;
52/*static*/ uint32_t GroupConfig::s_uGroupNo = 0;
53
54
55/**
56 * Configuration file exception.
57 */
58class ConfigFileError
59 : public RTCError
60{
61public:
62#if 0 /* This just confuses the compiler. */
63 ConfigFileError(const char *a_pszMessage)
64 : RTCError(a_pszMessage)
65 {}
66#endif
67
68 explicit ConfigFileError(xml::Node const *pNode, const char *a_pszMsgFmt, ...)
69 : RTCError((char *)NULL)
70 {
71
72 i_buildPath(pNode);
73 m_strMsg.append(": ");
74
75 va_list va;
76 va_start(va, a_pszMsgFmt);
77 m_strMsg.appendPrintfV(a_pszMsgFmt, va);
78 va_end(va);
79 }
80
81
82 ConfigFileError(const char *a_pszMsgFmt, ...)
83 : RTCError((char *)NULL)
84 {
85 va_list va;
86 va_start(va, a_pszMsgFmt);
87 m_strMsg.printfV(a_pszMsgFmt, va);
88 va_end(va);
89 }
90
91 ConfigFileError(const RTCString &a_rstrMessage)
92 : RTCError(a_rstrMessage)
93 {}
94
95private:
96 void i_buildPath(xml::Node const *pNode)
97 {
98 if (pNode)
99 {
100 i_buildPath(pNode->getParent());
101 m_strMsg.append('/');
102 m_strMsg.append(pNode->getName());
103 if (pNode->isElement() && pNode->getParent())
104 {
105 xml::ElementNode const *pElm = (xml::ElementNode const *)pNode;
106 for (xml::Node const *pAttrib = pElm->getFirstAttribute(); pAttrib != NULL;
107 pAttrib = pAttrib->getNextSibiling())
108 if (pAttrib->isAttribute())
109 {
110 m_strMsg.append("[@");
111 m_strMsg.append(pAttrib->getName());
112 m_strMsg.append('=');
113 m_strMsg.append(pAttrib->getValue());
114 m_strMsg.append(']');
115 }
116 }
117 }
118 }
119
120};
121
122
123/**
124 * Private default constructor, external users use factor methods.
125 */
126Config::Config()
127 : m_strHome()
128 , m_strNetwork()
129 , m_strTrunk()
130 , m_enmTrunkType(kIntNetTrunkType_Invalid)
131 , m_MacAddress()
132 , m_IPv4Address()
133 , m_IPv4Netmask()
134 , m_IPv4PoolFirst()
135 , m_IPv4PoolLast()
136 , m_GlobalConfig()
137 , m_GroupConfigs()
138 , m_HostConfigs()
139{
140}
141
142
143/**
144 * Initializes the object.
145 *
146 * @returns IPRT status code.
147 */
148int Config::i_init() RT_NOEXCEPT
149{
150 return i_homeInit();
151}
152
153
154/**
155 * Initializes the m_strHome member with the path to ~/.VirtualBox or equivalent.
156 *
157 * @returns IPRT status code.
158 * @todo Too many init functions?
159 */
160int Config::i_homeInit() RT_NOEXCEPT
161{
162 char szHome[RTPATH_MAX];
163 int rc = com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome), false);
164 if (RT_SUCCESS(rc))
165 rc = m_strHome.assignNoThrow(szHome);
166 else
167 DHCP_LOG_MSG_ERROR(("unable to locate the VirtualBox home directory: %Rrc\n", rc));
168 return rc;
169}
170
171
172/**
173 * Internal worker for the public factory methods that creates an instance and
174 * calls i_init() on it.
175 *
176 * @returns Config instance on success, NULL on failure.
177 */
178/*static*/ Config *Config::i_createInstanceAndCallInit() RT_NOEXCEPT
179{
180 Config *pConfig;
181 try
182 {
183 pConfig = new Config();
184 }
185 catch (std::bad_alloc &)
186 {
187 return NULL;
188 }
189
190 int rc = pConfig->i_init();
191 if (RT_SUCCESS(rc))
192 return pConfig;
193 delete pConfig;
194 return NULL;
195}
196
197
198/**
199 * Worker for i_complete() that initializes the release log of the process.
200 *
201 * Requires network name to be known as the log file name depends on
202 * it. Alternatively, consider passing the log file name via the
203 * command line?
204 *
205 * @note This is only used when no --log parameter was given.
206 */
207int Config::i_logInit() RT_NOEXCEPT
208{
209 if (g_fInitializedLog)
210 return VINF_SUCCESS;
211
212 if (m_strHome.isEmpty() || m_strNetwork.isEmpty())
213 return VERR_PATH_ZERO_LENGTH;
214
215 /* default log file name */
216 char szLogFile[RTPATH_MAX];
217 ssize_t cch = RTStrPrintf2(szLogFile, sizeof(szLogFile),
218 "%s%c%s-Dhcpd.log",
219 m_strHome.c_str(), RTPATH_DELIMITER, m_strNetwork.c_str());
220 if (cch > 0)
221 {
222 RTPathPurgeFilename(RTPathFilename(szLogFile), RTPATH_STR_F_STYLE_HOST);
223 return i_logInitWithFilename(szLogFile);
224 }
225 return VERR_BUFFER_OVERFLOW;
226}
227
228
229/**
230 * Worker for i_logInit and for handling --log on the command line.
231 *
232 * @returns IPRT status code.
233 * @param pszFilename The log filename.
234 */
235/*static*/ int Config::i_logInitWithFilename(const char *pszFilename) RT_NOEXCEPT
236{
237 AssertReturn(!g_fInitializedLog, VERR_WRONG_ORDER);
238
239 int rc = com::VBoxLogRelCreate("DHCP Server",
240 pszFilename,
241 RTLOGFLAGS_PREFIX_TIME_PROG,
242 "all net_dhcpd.e.l.f.l3.l4.l5.l6",
243 "VBOXDHCP_RELEASE_LOG",
244 RTLOGDEST_FILE
245#ifdef DEBUG
246 | RTLOGDEST_STDERR
247#endif
248 ,
249 32768 /* cMaxEntriesPerGroup */,
250 5 /* cHistory */,
251 RT_SEC_1DAY /* uHistoryFileTime */,
252 _32M /* uHistoryFileSize */,
253 NULL /* pErrInfo */);
254 if (RT_SUCCESS(rc))
255 g_fInitializedLog = true;
256 else
257 RTMsgError("Log initialization failed: %Rrc, log file '%s'", rc, pszFilename);
258 return rc;
259
260}
261
262
263/**
264 * Post process and validate the configuration after it has been loaded.
265 */
266int Config::i_complete() RT_NOEXCEPT
267{
268 if (m_strNetwork.isEmpty())
269 {
270 LogRel(("network name is not specified\n"));
271 return false;
272 }
273
274 i_logInit();
275
276 /** @todo the MAC address is always generated, no XML config option for it ... */
277 bool fMACGenerated = false;
278 if ( m_MacAddress.au16[0] == 0
279 && m_MacAddress.au16[1] == 0
280 && m_MacAddress.au16[2] == 0)
281 {
282 RTUUID Uuid;
283 int rc = RTUuidCreate(&Uuid);
284 AssertRCReturn(rc, rc);
285
286 m_MacAddress.au8[0] = 0x08;
287 m_MacAddress.au8[1] = 0x00;
288 m_MacAddress.au8[2] = 0x27;
289 m_MacAddress.au8[3] = Uuid.Gen.au8Node[3];
290 m_MacAddress.au8[4] = Uuid.Gen.au8Node[4];
291 m_MacAddress.au8[5] = Uuid.Gen.au8Node[5];
292
293 LogRel(("MAC address is not specified: will use generated MAC %RTmac\n", &m_MacAddress));
294 fMACGenerated = true;
295 }
296
297 /* unicast MAC address */
298 if (m_MacAddress.au8[0] & 0x01)
299 {
300 LogRel(("MAC address is not unicast: %RTmac\n", &m_MacAddress));
301 return VERR_GENERAL_FAILURE;
302 }
303
304 if (!fMACGenerated)
305 LogRel(("MAC address %RTmac\n", &m_MacAddress));
306
307 return VINF_SUCCESS;
308}
309
310
311/**
312 * Parses the command line and loads the configuration.
313 *
314 * @returns The configuration, NULL if we ran into some fatal problem.
315 * @param argc The argc from main().
316 * @param argv The argv from main().
317 */
318Config *Config::create(int argc, char **argv) RT_NOEXCEPT
319{
320 /*
321 * Parse the command line.
322 */
323 static const RTGETOPTDEF s_aOptions[] =
324 {
325 { "--comment", '#', RTGETOPT_REQ_STRING },
326 { "--config", 'c', RTGETOPT_REQ_STRING },
327 { "--log", 'l', RTGETOPT_REQ_STRING },
328 { "--log-destinations", 'd', RTGETOPT_REQ_STRING },
329 { "--log-flags", 'f', RTGETOPT_REQ_STRING },
330 { "--log-group-settings", 'g', RTGETOPT_REQ_STRING },
331 { "--relaxed", 'r', RTGETOPT_REQ_NOTHING },
332 { "--strict", 's', RTGETOPT_REQ_NOTHING },
333 };
334
335 RTGETOPTSTATE State;
336 int rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
337 AssertRCReturn(rc, NULL);
338
339 const char *pszLogFile = NULL;
340 const char *pszLogGroupSettings = NULL;
341 const char *pszLogDestinations = NULL;
342 const char *pszLogFlags = NULL;
343 const char *pszConfig = NULL;
344 const char *pszComment = NULL;
345 bool fStrict = true;
346
347 for (;;)
348 {
349 RTGETOPTUNION ValueUnion;
350 rc = RTGetOpt(&State, &ValueUnion);
351 if (rc == 0) /* done */
352 break;
353
354 switch (rc)
355 {
356 case 'c': /* --config */
357 pszConfig = ValueUnion.psz;
358 break;
359
360 case 'l':
361 pszLogFile = ValueUnion.psz;
362 break;
363
364 case 'd':
365 pszLogDestinations = ValueUnion.psz;
366 break;
367
368 case 'f':
369 pszLogFlags = ValueUnion.psz;
370 break;
371
372 case 'g':
373 pszLogGroupSettings = ValueUnion.psz;
374 break;
375
376 case 'r':
377 fStrict = false;
378 break;
379
380 case 's':
381 fStrict = true;
382 break;
383
384 case '#': /* --comment */
385 /* The sole purpose of this option is to allow identification of DHCP
386 * server instances in the process list. We ignore the required string
387 * argument of this option. */
388 pszComment = ValueUnion.psz;
389 break;
390
391 default:
392 RTGetOptPrintError(rc, &ValueUnion);
393 return NULL;
394 }
395 }
396
397 if (!pszConfig)
398 {
399 RTMsgError("No configuration file specified (--config file)!\n");
400 return NULL;
401 }
402
403 /*
404 * Init the log if a log file was specified.
405 */
406 if (pszLogFile)
407 {
408 rc = i_logInitWithFilename(pszLogFile);
409 if (RT_FAILURE(rc))
410 RTMsgError("Failed to initialize log file '%s': %Rrc", pszLogFile, rc);
411
412 if (pszLogDestinations)
413 RTLogDestinations(RTLogRelGetDefaultInstance(), pszLogDestinations);
414 if (pszLogFlags)
415 RTLogFlags(RTLogRelGetDefaultInstance(), pszLogFlags);
416 if (pszLogGroupSettings)
417 RTLogGroupSettings(RTLogRelGetDefaultInstance(), pszLogGroupSettings);
418
419 LogRel(("--config: %s\n", pszComment));
420 if (pszComment)
421 LogRel(("--comment: %s\n", pszComment));
422 }
423
424 /*
425 * Read the config file.
426 */
427 RTMsgInfo("reading config from '%s'...\n", pszConfig);
428 std::unique_ptr<Config> ptrConfig;
429 ptrConfig.reset(Config::i_read(pszConfig, fStrict));
430 if (ptrConfig.get() != NULL)
431 {
432 rc = ptrConfig->i_complete();
433 if (RT_SUCCESS(rc))
434 return ptrConfig.release();
435 }
436 return NULL;
437}
438
439
440/**
441 *
442 * @note The release log is not operational when this method is called.
443 */
444Config *Config::i_read(const char *pszFileName, bool fStrict) RT_NOEXCEPT
445{
446 if (pszFileName == NULL || pszFileName[0] == '\0')
447 {
448 DHCP_LOG_MSG_ERROR(("Config::i_read: Empty configuration filename\n"));
449 return NULL;
450 }
451
452 xml::Document doc;
453 try
454 {
455 xml::XmlFileParser parser;
456 parser.read(pszFileName, doc);
457 }
458 catch (const xml::EIPRTFailure &e)
459 {
460 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
461 return NULL;
462 }
463 catch (const RTCError &e)
464 {
465 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
466 return NULL;
467 }
468 catch (...)
469 {
470 DHCP_LOG_MSG_ERROR(("Config::i_read: Unknown exception while reading and parsing '%s'\n", pszFileName));
471 return NULL;
472 }
473
474 std::unique_ptr<Config> config(i_createInstanceAndCallInit());
475 AssertReturn(config.get() != NULL, NULL);
476
477 try
478 {
479 config->i_parseConfig(doc.getRootElement(), fStrict);
480 }
481 catch (const RTCError &e)
482 {
483 DHCP_LOG_MSG_ERROR(("Config::i_read: %s\n", e.what()));
484 return NULL;
485 }
486 catch (std::bad_alloc &)
487 {
488 DHCP_LOG_MSG_ERROR(("Config::i_read: std::bad_alloc\n"));
489 return NULL;
490 }
491 catch (...)
492 {
493 DHCP_LOG_MSG_ERROR(("Config::i_read: Unexpected exception\n"));
494 return NULL;
495 }
496
497 return config.release();
498}
499
500
501/**
502 * Helper for retrieving a IPv4 attribute.
503 *
504 * @param pElm The element to get the attribute from.
505 * @param pszAttrName The name of the attribute
506 * @param pAddr Where to return the address.
507 * @throws ConfigFileError
508 */
509static void getIPv4AddrAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTNETADDRIPV4 pAddr)
510{
511 const char *pszAttrValue;
512 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
513 {
514 int rc = RTNetStrToIPv4Addr(pszAttrValue, pAddr);
515 if (RT_SUCCESS(rc))
516 return;
517 throw ConfigFileError(pElm, "Attribute %s is not a valid IPv4 address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
518 }
519 throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
520}
521
522
523/**
524 * Helper for retrieving a MAC address attribute.
525 *
526 * @param pElm The element to get the attribute from.
527 * @param pszAttrName The name of the attribute
528 * @param pMacAddr Where to return the MAC address.
529 * @throws ConfigFileError
530 */
531static void getMacAddressAttribute(const xml::ElementNode *pElm, const char *pszAttrName, PRTMAC pMacAddr)
532{
533 const char *pszAttrValue;
534 if (pElm->getAttributeValue(pszAttrName, &pszAttrValue))
535 {
536 int rc = RTNetStrToMacAddr(pszAttrValue, pMacAddr);
537 if (RT_SUCCESS(rc) && rc != VWRN_TRAILING_CHARS)
538 return;
539 throw ConfigFileError(pElm, "attribute %s is not a valid MAC address: '%s' -> %Rrc", pszAttrName, pszAttrValue, rc);
540 }
541 throw ConfigFileError(pElm, "Required %s attribute missing", pszAttrName);
542}
543
544
545/**
546 * Internal worker for i_read() that parses the root element and everything
547 * below it.
548 *
549 * @param pElmRoot The root element.
550 * @param fStrict Set if we're in strict mode, clear if we just
551 * want to get on with it if we can.
552 * @throws std::bad_alloc, ConfigFileError
553 */
554void Config::i_parseConfig(const xml::ElementNode *pElmRoot, bool fStrict)
555{
556 /*
557 * Check the root element and call i_parseServer to do real work.
558 */
559 if (pElmRoot == NULL)
560 throw ConfigFileError("Empty config file");
561
562 /** @todo XXX: NAMESPACE API IS COMPLETELY BROKEN, SO IGNORE IT FOR NOW */
563
564 if (!pElmRoot->nameEquals("DHCPServer"))
565 throw ConfigFileError("Unexpected root element '%s'", pElmRoot->getName());
566
567 i_parseServer(pElmRoot, fStrict);
568
569#if 0 /** @todo convert to LogRel2 stuff */
570 // XXX: debug
571 for (optmap_t::const_iterator it = m_GlobalOptions.begin(); it != m_GlobalOptions.end(); ++it) {
572 std::shared_ptr<DhcpOption> opt(it->second);
573
574 octets_t data;
575 opt->encode(data);
576
577 bool space = false;
578 for (octets_t::const_iterator itData = data.begin(); itData != data.end(); ++itData) {
579 uint8_t c = *itData;
580 if (space)
581 std::cout << " ";
582 else
583 space = true;
584 std::cout << (int)c;
585 }
586 std::cout << std::endl;
587 }
588#endif
589}
590
591
592/**
593 * Internal worker for parsing the elements under /DHCPServer/.
594 *
595 * @param pElmServer The DHCPServer element.
596 * @param fStrict Set if we're in strict mode, clear if we just
597 * want to get on with it if we can.
598 * @throws std::bad_alloc, ConfigFileError
599 */
600void Config::i_parseServer(const xml::ElementNode *pElmServer, bool fStrict)
601{
602 /*
603 * <DHCPServer> attributes
604 */
605 if (!pElmServer->getAttributeValue("networkName", m_strNetwork))
606 throw ConfigFileError("DHCPServer/@networkName missing");
607 if (m_strNetwork.isEmpty())
608 throw ConfigFileError("DHCPServer/@networkName is empty");
609
610 const char *pszTrunkType;
611 if (!pElmServer->getAttributeValue("trunkType", &pszTrunkType))
612 throw ConfigFileError("DHCPServer/@trunkType missing");
613 if (strcmp(pszTrunkType, "none") == 0)
614 m_enmTrunkType = kIntNetTrunkType_None;
615 else if (strcmp(pszTrunkType, "whatever") == 0)
616 m_enmTrunkType = kIntNetTrunkType_WhateverNone;
617 else if (strcmp(pszTrunkType, "netflt") == 0)
618 m_enmTrunkType = kIntNetTrunkType_NetFlt;
619 else if (strcmp(pszTrunkType, "netadp") == 0)
620 m_enmTrunkType = kIntNetTrunkType_NetAdp;
621 else
622 throw ConfigFileError("Invalid DHCPServer/@trunkType value: %s", pszTrunkType);
623
624 if ( m_enmTrunkType == kIntNetTrunkType_NetFlt
625 || m_enmTrunkType == kIntNetTrunkType_NetAdp)
626 {
627 if (!pElmServer->getAttributeValue("trunkName", &m_strTrunk))
628 throw ConfigFileError("DHCPServer/@trunkName missing");
629 }
630 else
631 m_strTrunk = "";
632
633 m_strLeasesFilename = pElmServer->findAttributeValue("leasesFilename"); /* optional */
634 if (m_strLeasesFilename.isEmpty())
635 {
636 int rc = m_strLeasesFilename.assignNoThrow(getHome());
637 if (RT_SUCCESS(rc))
638 rc = RTPathAppendCxx(m_strLeasesFilename, m_strNetwork);
639 if (RT_SUCCESS(rc))
640 rc = m_strLeasesFilename.appendNoThrow("-Dhcpd.leases");
641 if (RT_FAILURE(rc))
642 throw ConfigFileError("Unexpected error constructing default m_strLeasesFilename value: %Rrc", rc);
643 RTPathPurgeFilename(RTPathFilename(m_strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
644 m_strLeasesFilename.jolt();
645 }
646
647 /*
648 * Addresses and mask.
649 */
650 ::getIPv4AddrAttribute(pElmServer, "IPAddress", &m_IPv4Address);
651 ::getIPv4AddrAttribute(pElmServer, "networkMask", &m_IPv4Netmask);
652 ::getIPv4AddrAttribute(pElmServer, "lowerIP", &m_IPv4PoolFirst);
653 ::getIPv4AddrAttribute(pElmServer, "upperIP", &m_IPv4PoolLast);
654
655 /* unicast IP address */
656 if ((m_IPv4Address.au8[0] & 0xe0) == 0xe0)
657 throw ConfigFileError("DHCP server IP address is not unicast: %RTnaipv4", m_IPv4Address.u);
658
659 /* valid netmask */
660 int cPrefixBits;
661 int rc = RTNetMaskToPrefixIPv4(&m_IPv4Netmask, &cPrefixBits);
662 if (RT_FAILURE(rc) || cPrefixBits == 0)
663 throw ConfigFileError("IP mask is not valid: %RTnaipv4", m_IPv4Netmask.u);
664
665 /* first IP is from the same network */
666 if ((m_IPv4PoolFirst.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
667 throw ConfigFileError("first pool address is outside the network %RTnaipv4/%d: %RTnaipv4",
668 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolFirst.u);
669
670 /* last IP is from the same network */
671 if ((m_IPv4PoolLast.u & m_IPv4Netmask.u) != (m_IPv4Address.u & m_IPv4Netmask.u))
672 throw ConfigFileError("last pool address is outside the network %RTnaipv4/%d: %RTnaipv4\n",
673 (m_IPv4Address.u & m_IPv4Netmask.u), cPrefixBits, m_IPv4PoolLast.u);
674
675 /* the pool is valid */
676 if (RT_N2H_U32(m_IPv4PoolLast.u) < RT_N2H_U32(m_IPv4PoolFirst.u))
677 throw ConfigFileError("pool range is invalid: %RTnaipv4 - %RTnaipv4", m_IPv4PoolFirst.u, m_IPv4PoolLast.u);
678 LogRel(("IP address: %RTnaipv4/%d\n", m_IPv4Address.u, cPrefixBits));
679 LogRel(("Address pool: %RTnaipv4 - %RTnaipv4\n", m_IPv4PoolFirst.u, m_IPv4PoolLast.u));
680
681 /*
682 * <DHCPServer> children
683 */
684 xml::NodesLoop it(*pElmServer);
685 const xml::ElementNode *pElmChild;
686 while ((pElmChild = it.forAllNodes()) != NULL)
687 {
688 /* Global options: */
689 if (pElmChild->nameEquals("Options"))
690 m_GlobalConfig.initFromXml(pElmChild, fStrict, this);
691 /* Group w/ options: */
692 else if (pElmChild->nameEquals("Group"))
693 {
694 std::unique_ptr<GroupConfig> ptrGroup(new GroupConfig());
695 ptrGroup->initFromXml(pElmChild, fStrict, this);
696 if (m_GroupConfigs.find(ptrGroup->getGroupName()) == m_GroupConfigs.end())
697 {
698 m_GroupConfigs[ptrGroup->getGroupName()] = ptrGroup.get();
699 ptrGroup.release();
700 }
701 else if (!fStrict)
702 LogRelFunc(("Ignoring duplicate group name: %s", ptrGroup->getGroupName().c_str()));
703 else
704 throw ConfigFileError("Duplicate group name: %s", ptrGroup->getGroupName().c_str());
705 }
706 /*
707 * MAC address and per VM NIC configurations:
708 */
709 else if (pElmChild->nameEquals("Config"))
710 {
711 std::unique_ptr<HostConfig> ptrHost(new HostConfig());
712 ptrHost->initFromXml(pElmChild, fStrict, this);
713 if (m_HostConfigs.find(ptrHost->getMACAddress()) == m_HostConfigs.end())
714 {
715 m_HostConfigs[ptrHost->getMACAddress()] = ptrHost.get();
716 ptrHost.release();
717 }
718 else if (!fStrict)
719 LogRelFunc(("Ignorining duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress()));
720 else
721 throw ConfigFileError("Duplicate MAC address (Config): %RTmac", &ptrHost->getMACAddress());
722 }
723 else if (!fStrict)
724 LogRel(("Ignoring unexpected DHCPServer child: %s\n", pElmChild->getName()));
725 else
726 throw ConfigFileError("Unexpected DHCPServer child <%s>'", pElmChild->getName());
727 }
728}
729
730
731/**
732 * Internal worker for parsing \<Option\> elements found under
733 * /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/.
734 *
735 * @param pElmOption An \<Option\> element.
736 * @throws std::bad_alloc, ConfigFileError
737 */
738void ConfigLevelBase::i_parseOption(const xml::ElementNode *pElmOption)
739{
740 /* The 'name' attribute: */
741 const char *pszName;
742 if (!pElmOption->getAttributeValue("name", &pszName))
743 throw ConfigFileError(pElmOption, "missing option name");
744
745 uint8_t u8Opt;
746 int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
747 if (rc != VINF_SUCCESS) /* no warnings either */
748 throw ConfigFileError(pElmOption, "Bad option name '%s': %Rrc", pszName, rc);
749
750 /* The optional 'encoding' attribute: */
751 uint32_t u32Enc = 0; /* XXX: DHCPOptionEncoding_Normal */
752 const char *pszEncoding;
753 if (pElmOption->getAttributeValue("encoding", &pszEncoding))
754 {
755 rc = RTStrToUInt32Full(pszEncoding, 10, &u32Enc);
756 if (rc != VINF_SUCCESS) /* no warnings either */
757 throw ConfigFileError(pElmOption, "Bad option encoding '%s': %Rrc", pszEncoding, rc);
758
759 switch (u32Enc)
760 {
761 case 0: /* XXX: DHCPOptionEncoding_Normal */
762 case 1: /* XXX: DHCPOptionEncoding_Hex */
763 break;
764 default:
765 throw ConfigFileError(pElmOption, "Unknown encoding '%s'", pszEncoding);
766 }
767 }
768
769 /* The 'value' attribute. May be omitted for OptNoValue options like rapid commit. */
770 const char *pszValue;
771 if (!pElmOption->getAttributeValue("value", &pszValue))
772 pszValue = "";
773
774 /** @todo XXX: TODO: encoding, handle hex */
775 DhcpOption *opt = DhcpOption::parse(u8Opt, u32Enc, pszValue);
776 if (opt == NULL)
777 throw ConfigFileError(pElmOption, "Bad option '%s' (encoding %u): '%s' ", pszName, u32Enc, pszValue ? pszValue : "");
778
779 /* Add it to the map: */
780 m_Options << opt;
781}
782
783
784/**
785 * Internal worker for parsing \<ForcedOption\> and \<SupcressedOption\> elements
786 * found under /DHCPServer/Options/, /DHCPServer/Group/ and /DHCPServer/Config/.
787 *
788 * @param pElmOption The element.
789 * @param fForced Whether it's a ForcedOption (true) or
790 * SuppressedOption element.
791 * @throws std::bad_alloc, ConfigFileError
792 */
793void ConfigLevelBase::i_parseForcedOrSuppressedOption(const xml::ElementNode *pElmOption, bool fForced)
794{
795 /* Only a name attribute: */
796 const char *pszName;
797 if (!pElmOption->getAttributeValue("name", &pszName))
798 throw ConfigFileError(pElmOption, "missing option name");
799
800 uint8_t u8Opt;
801 int rc = RTStrToUInt8Full(pszName, 10, &u8Opt);
802 if (rc != VINF_SUCCESS) /* no warnings either */
803 throw ConfigFileError(pElmOption, "Bad option name '%s': %Rrc", pszName, rc);
804
805 if (fForced)
806 m_vecForcedOptions.push_back(u8Opt);
807 else
808 m_vecSuppressedOptions.push_back(u8Opt);
809}
810
811
812/**
813 * Final children parser, handling only \<Option\> and barfing at anything else.
814 *
815 * @param pElmChild The child element to handle.
816 * @param fStrict Set if we're in strict mode, clear if we just
817 * want to get on with it if we can. That said,
818 * the caller will catch ConfigFileError exceptions
819 * and ignore them if @a fStrict is @c false.
820 * @param pConfig The configuration object.
821 * @throws std::bad_alloc, ConfigFileError
822 */
823void ConfigLevelBase::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig)
824{
825 /*
826 * Options.
827 */
828 if (pElmChild->nameEquals("Option"))
829 {
830 i_parseOption(pElmChild);
831 return;
832 }
833
834 /*
835 * Forced and suppressed options.
836 */
837 bool const fForced = pElmChild->nameEquals("ForcedOption");
838 if (fForced || pElmChild->nameEquals("SuppressedOption"))
839 {
840 i_parseForcedOrSuppressedOption(pElmChild, fForced);
841 return;
842 }
843
844 /*
845 * What's this?
846 */
847 throw ConfigFileError(pElmChild->getParent(), "Unexpected child '%s'", pElmChild->getName());
848 RT_NOREF(fStrict, pConfig);
849}
850
851
852/**
853 * Base class initialization taking a /DHCPServer/Options, /DHCPServer/Group or
854 * /DHCPServer/Config element as input and handling common attributes as well as
855 * any \<Option\> children.
856 *
857 * @param pElmConfig The configuration element to parse.
858 * @param fStrict Set if we're in strict mode, clear if we just
859 * want to get on with it if we can.
860 * @param pConfig The configuration object.
861 * @throws std::bad_alloc, ConfigFileError
862 */
863void ConfigLevelBase::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict, Config const *pConfig)
864{
865 /*
866 * Common attributes:
867 */
868 if (!pElmConfig->getAttributeValue("secMinLeaseTime", &m_secMinLeaseTime))
869 m_secMinLeaseTime = 0;
870 if (!pElmConfig->getAttributeValue("secDefaultLeaseTime", &m_secDefaultLeaseTime))
871 m_secDefaultLeaseTime = 0;
872 if (!pElmConfig->getAttributeValue("secMaxLeaseTime", &m_secMaxLeaseTime))
873 m_secMaxLeaseTime = 0;
874
875 /* Swap min and max if max is smaller: */
876 if (m_secMaxLeaseTime < m_secMinLeaseTime && m_secMinLeaseTime && m_secMaxLeaseTime)
877 {
878 LogRel(("Swapping min/max lease times: %u <-> %u\n", m_secMinLeaseTime, m_secMaxLeaseTime));
879 uint32_t uTmp = m_secMaxLeaseTime;
880 m_secMaxLeaseTime = m_secMinLeaseTime;
881 m_secMinLeaseTime = uTmp;
882 }
883
884 /*
885 * Parse children.
886 */
887 xml::NodesLoop it(*pElmConfig);
888 const xml::ElementNode *pElmChild;
889 while ((pElmChild = it.forAllNodes()) != NULL)
890 {
891 try
892 {
893 i_parseChild(pElmChild, fStrict, pConfig);
894 }
895 catch (ConfigFileError &rXcpt)
896 {
897 if (fStrict)
898 throw rXcpt;
899 LogRelFunc(("Ignoring: %s\n", rXcpt.what()));
900 }
901 }
902}
903
904
905/**
906 * Internal worker for parsing the elements under /DHCPServer/Options/.
907 *
908 * @param pElmOptions The \<Options\> element.
909 * @param fStrict Set if we're in strict mode, clear if we just
910 * want to get on with it if we can.
911 * @param pConfig The configuration object.
912 * @throws std::bad_alloc, ConfigFileError
913 */
914void GlobalConfig::initFromXml(const xml::ElementNode *pElmOptions, bool fStrict, Config const *pConfig)
915{
916 ConfigLevelBase::initFromXml(pElmOptions, fStrict, pConfig);
917
918 /*
919 * Resolve defaults here in the global config so we don't have to do this
920 * in Db::allocateBinding() for every lease request.
921 */
922 if (m_secMaxLeaseTime == 0 && m_secDefaultLeaseTime == 0 && m_secMinLeaseTime == 0)
923 {
924 m_secMinLeaseTime = 300; /* 5 min */
925 m_secDefaultLeaseTime = 600; /* 10 min */
926 m_secMaxLeaseTime = 12 * RT_SEC_1HOUR; /* 12 hours */
927 }
928 else
929 {
930 if (m_secDefaultLeaseTime == 0)
931 {
932 if (m_secMaxLeaseTime != 0)
933 m_secDefaultLeaseTime = RT_MIN(RT_MAX(m_secMinLeaseTime, 600), m_secMaxLeaseTime);
934 else
935 {
936 m_secDefaultLeaseTime = RT_MAX(m_secMinLeaseTime, 600);
937 m_secMaxLeaseTime = RT_MAX(m_secDefaultLeaseTime, 12 * RT_SEC_1HOUR);
938 }
939 }
940 if (m_secMaxLeaseTime == 0)
941 m_secMaxLeaseTime = RT_MAX(RT_MAX(m_secMinLeaseTime, m_secDefaultLeaseTime), 12 * RT_SEC_1HOUR);
942 if (m_secMinLeaseTime == 0)
943 m_secMinLeaseTime = RT_MIN(300, m_secDefaultLeaseTime);
944 }
945
946}
947
948
949/**
950 * Overrides base class to handle the condition elements under \<Group\>.
951 *
952 * @param pElmChild The child element.
953 * @param fStrict Set if we're in strict mode, clear if we just
954 * want to get on with it if we can.
955 * @param pConfig The configuration object.
956 * @throws std::bad_alloc, ConfigFileError
957 */
958void GroupConfig::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict, Config const *pConfig)
959{
960 /*
961 * Match the condition
962 */
963 std::unique_ptr<GroupCondition> ptrCondition;
964 if (pElmChild->nameEquals("ConditionMAC"))
965 ptrCondition.reset(new GroupConditionMAC());
966 else if (pElmChild->nameEquals("ConditionMACWildcard"))
967 ptrCondition.reset(new GroupConditionMACWildcard());
968 else if (pElmChild->nameEquals("ConditionVendorClassID"))
969 ptrCondition.reset(new GroupConditionVendorClassID());
970 else if (pElmChild->nameEquals("ConditionVendorClassIDWildcard"))
971 ptrCondition.reset(new GroupConditionVendorClassIDWildcard());
972 else if (pElmChild->nameEquals("ConditionUserClassID"))
973 ptrCondition.reset(new GroupConditionUserClassID());
974 else if (pElmChild->nameEquals("ConditionUserClassIDWildcard"))
975 ptrCondition.reset(new GroupConditionUserClassIDWildcard());
976 else
977 {
978 /*
979 * Not a condition, pass it on to the base class.
980 */
981 ConfigLevelBase::i_parseChild(pElmChild, fStrict, pConfig);
982 return;
983 }
984
985 /*
986 * Get the attributes and initialize the condition.
987 */
988 bool fInclusive;
989 if (!pElmChild->getAttributeValue("inclusive", fInclusive))
990 fInclusive = true;
991 const char *pszValue = pElmChild->findAttributeValue("value");
992 if (pszValue && *pszValue)
993 {
994 int rc = ptrCondition->initCondition(pszValue, fInclusive);
995 if (RT_SUCCESS(rc))
996 {
997 /*
998 * Add it to the appropriate vector.
999 */
1000 if (fInclusive)
1001 m_Inclusive.push_back(ptrCondition.release());
1002 else
1003 m_Exclusive.push_back(ptrCondition.release());
1004 }
1005 else
1006 {
1007 ConfigFileError Xcpt(pElmChild, "initCondition failed with %Rrc for '%s' and %RTbool", rc, pszValue, fInclusive);
1008 if (!fStrict)
1009 LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
1010 else
1011 throw ConfigFileError(Xcpt);
1012 }
1013 }
1014 else
1015 {
1016 ConfigFileError Xcpt(pElmChild, "condition value is empty or missing (inclusive=%RTbool)", fInclusive);
1017 if (fStrict)
1018 throw Xcpt;
1019 LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
1020 }
1021}
1022
1023
1024/**
1025 * Internal worker for parsing the elements under /DHCPServer/Group/.
1026 *
1027 * @param pElmGroup The \<Group\> element.
1028 * @param fStrict Set if we're in strict mode, clear if we just
1029 * want to get on with it if we can.
1030 * @param pConfig The configuration object.
1031 * @throws std::bad_alloc, ConfigFileError
1032 */
1033void GroupConfig::initFromXml(const xml::ElementNode *pElmGroup, bool fStrict, Config const *pConfig)
1034{
1035 /*
1036 * Attributes:
1037 */
1038 if (!pElmGroup->getAttributeValue("name", m_strName) || m_strName.isEmpty())
1039 {
1040 if (fStrict)
1041 throw ConfigFileError(pElmGroup, "Group as no name or the name is empty");
1042 m_strName.printf("Group#%u", s_uGroupNo++);
1043 }
1044
1045 /*
1046 * Do common initialization (including children).
1047 */
1048 ConfigLevelBase::initFromXml(pElmGroup, fStrict, pConfig);
1049}
1050
1051
1052/**
1053 * Internal worker for parsing the elements under /DHCPServer/Config/.
1054 *
1055 * VM Config entries are generated automatically from VirtualBox.xml
1056 * with the MAC fetched from the VM config. The client id is nowhere
1057 * in the picture there, so VM config is indexed with plain RTMAC, not
1058 * ClientId (also see getOptions below).
1059 *
1060 * @param pElmConfig The \<Config\> element.
1061 * @param fStrict Set if we're in strict mode, clear if we just
1062 * want to get on with it if we can.
1063 * @param pConfig The configuration object (for netmask).
1064 * @throws std::bad_alloc, ConfigFileError
1065 */
1066void HostConfig::initFromXml(const xml::ElementNode *pElmConfig, bool fStrict, Config const *pConfig)
1067{
1068 /*
1069 * Attributes:
1070 */
1071 /* The MAC address: */
1072 ::getMacAddressAttribute(pElmConfig, "MACAddress", &m_MACAddress);
1073
1074 /* Name - optional: */
1075 if (!pElmConfig->getAttributeValue("name", m_strName))
1076 m_strName.printf("MAC:%RTmac", &m_MACAddress);
1077
1078 /* Fixed IP address assignment - optional: */
1079 const char *pszFixedAddress = pElmConfig->findAttributeValue("fixedAddress");
1080 if (!pszFixedAddress || *RTStrStripL(pszFixedAddress) == '\0')
1081 m_fHaveFixedAddress = false;
1082 else
1083 {
1084 ::getIPv4AddrAttribute(pElmConfig, "fixedAddress", &m_FixedAddress);
1085 if (pConfig->isInIPv4Network(m_FixedAddress))
1086 m_fHaveFixedAddress = true;
1087 else
1088 {
1089 ConfigFileError Xcpt(pElmConfig, "fixedAddress '%s' is not the DHCP network", pszFixedAddress);
1090 if (fStrict)
1091 throw Xcpt;
1092 LogRelFunc(("%s - ignoring the fixed address assignment\n", Xcpt.what()));
1093 m_fHaveFixedAddress = false;
1094 }
1095 }
1096
1097 /*
1098 * Do common initialization.
1099 */
1100 ConfigLevelBase::initFromXml(pElmConfig, fStrict, pConfig);
1101}
1102
1103
1104/**
1105 * Assembles a list of hosts with fixed address assignments.
1106 *
1107 * @returns IPRT status code.
1108 * @param a_rRetConfigs Where to return the configurations.
1109 */
1110int Config::getFixedAddressConfigs(HostConfigVec &a_rRetConfigs) const
1111{
1112 for (HostConfigMap::const_iterator it = m_HostConfigs.begin(); it != m_HostConfigs.end(); ++it)
1113 {
1114 HostConfig const *pHostConfig = it->second;
1115 if (pHostConfig->haveFixedAddress())
1116 try
1117 {
1118 a_rRetConfigs.push_back(pHostConfig);
1119 }
1120 catch (std::bad_alloc &)
1121 {
1122 return VERR_NO_MEMORY;
1123 }
1124 }
1125 return VINF_SUCCESS;
1126}
1127
1128
1129/**
1130 * Assembles a priorities vector of configurations for the client.
1131 *
1132 * @returns a_rRetConfigs for convenience.
1133 * @param a_rRetConfigs Where to return the configurations.
1134 * @param a_ridClient The client ID.
1135 * @param a_ridVendorClass The vendor class ID if present.
1136 * @param a_ridUserClass The user class ID if present
1137 */
1138Config::ConfigVec &Config::getConfigsForClient(Config::ConfigVec &a_rRetConfigs, const ClientId &a_ridClient,
1139 const OptVendorClassId &a_ridVendorClass,
1140 const OptUserClassId &a_ridUserClass) const
1141{
1142 /* Host specific config first: */
1143 HostConfigMap::const_iterator itHost = m_HostConfigs.find(a_ridClient.mac());
1144 if (itHost != m_HostConfigs.end())
1145 a_rRetConfigs.push_back(itHost->second);
1146
1147 /* Groups: */
1148 for (GroupConfigMap::const_iterator itGrp = m_GroupConfigs.begin(); itGrp != m_GroupConfigs.end(); ++itGrp)
1149 if (itGrp->second->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1150 a_rRetConfigs.push_back(itGrp->second);
1151
1152 /* Global: */
1153 a_rRetConfigs.push_back(&m_GlobalConfig);
1154
1155 return a_rRetConfigs;
1156}
1157
1158
1159/**
1160 * Method used by DHCPD to assemble a list of options for the client.
1161 *
1162 * @returns a_rRetOpts for convenience
1163 * @param a_rRetOpts Where to put the requested options.
1164 * @param a_rReqOpts The requested options.
1165 * @param a_rConfigs Relevant configurations returned by
1166 * Config::getConfigsForClient().
1167 *
1168 * @throws std::bad_alloc
1169 */
1170optmap_t &Config::getOptionsForClient(optmap_t &a_rRetOpts, const OptParameterRequest &a_rReqOpts, ConfigVec &a_rConfigs) const
1171{
1172 /*
1173 * The client typically requests a list of options. The list is subject to
1174 * forced and suppressed lists on each configuration level in a_rConfig. To
1175 * efficiently manage it without resorting to maps, the current code
1176 * assembles a C-style array of options on the stack that should be returned
1177 * to the client.
1178 */
1179 uint8_t abOptions[256];
1180 size_t cOptions = 0;
1181 size_t iFirstForced = 255;
1182#define IS_OPTION_PRESENT(a_bOption) (memchr(abOptions, (a_bOption), cOptions) != NULL)
1183#define APPEND_NOT_PRESENT_OPTION(a_bOption) do { \
1184 AssertLogRelMsgBreak(cOptions < sizeof(abOptions), \
1185 ("a_bOption=%#x abOptions=%.*Rhxs\n", (a_bOption), sizeof(abOptions), &abOptions[0])); \
1186 abOptions[cOptions++] = (a_bOption); \
1187 } while (0)
1188
1189 const OptParameterRequest::value_t &reqValue = a_rReqOpts.value();
1190 if (reqValue.size() != 0)
1191 {
1192 /* Copy the requested list and append any forced options from the configs: */
1193 for (octets_t::const_iterator itOptReq = reqValue.begin(); itOptReq != reqValue.end(); ++itOptReq)
1194 if (!IS_OPTION_PRESENT(*itOptReq))
1195 APPEND_NOT_PRESENT_OPTION(*itOptReq);
1196 iFirstForced = cOptions;
1197 for (Config::ConfigVec::const_iterator itCfg = a_rConfigs.begin(); itCfg != a_rConfigs.end(); ++itCfg)
1198 {
1199 octets_t const &rForced = (*itCfg)->getForcedOptions();
1200 for (octets_t::const_iterator itOpt = rForced.begin(); itOpt != rForced.end(); ++itOpt)
1201 if (!IS_OPTION_PRESENT(*itOpt))
1202 {
1203 LogRel3((">>> Forcing option %d (%s)\n", *itOpt, DhcpOption::name(*itOpt)));
1204 APPEND_NOT_PRESENT_OPTION(*itOpt);
1205 }
1206 }
1207 }
1208 else
1209 {
1210 /* No options requests, feed the client all available options: */
1211 for (Config::ConfigVec::const_iterator itCfg = a_rConfigs.begin(); itCfg != a_rConfigs.end(); ++itCfg)
1212 {
1213 optmap_t const &rOptions = (*itCfg)->getOptions();
1214 for (optmap_t::const_iterator itOpt = rOptions.begin(); itOpt != rOptions.end(); ++itOpt)
1215 if (!IS_OPTION_PRESENT(itOpt->first))
1216 APPEND_NOT_PRESENT_OPTION(itOpt->first);
1217
1218 }
1219 }
1220
1221 /*
1222 * Always supply the subnet:
1223 */
1224 a_rRetOpts << new OptSubnetMask(m_IPv4Netmask);
1225
1226 /*
1227 * Try provide the options we've decided to return.
1228 */
1229 for (size_t iOpt = 0; iOpt < cOptions; iOpt++)
1230 {
1231 uint8_t const bOptReq = abOptions[iOpt];
1232 if (iOpt < iFirstForced)
1233 LogRel2((">>> requested option %d (%s)\n", bOptReq, DhcpOption::name(bOptReq)));
1234 else
1235 LogRel2((">>> forced option %d (%s)\n", bOptReq, DhcpOption::name(bOptReq)));
1236
1237 if (bOptReq != OptSubnetMask::optcode)
1238 {
1239 bool fFound = false;
1240 for (size_t i = 0; i < a_rConfigs.size(); i++)
1241 {
1242 if (!a_rConfigs[i]->isOptionSuppressed(bOptReq))
1243 {
1244 optmap_t::const_iterator itFound;
1245 if (a_rConfigs[i]->findOption(bOptReq, itFound)) /* crap interface */
1246 {
1247 LogRel2(("... found in %s (type %s)\n", a_rConfigs[i]->getName(), a_rConfigs[i]->getType()));
1248 a_rRetOpts << itFound->second;
1249 fFound = true;
1250 break;
1251 }
1252 }
1253 else
1254 {
1255 LogRel2(("... suppressed by %s (type %s)\n", a_rConfigs[i]->getName(), a_rConfigs[i]->getType()));
1256 fFound = true;
1257 break;
1258 }
1259 }
1260 if (!fFound)
1261 LogRel3(("... not found\n"));
1262 }
1263 else
1264 LogRel2(("... always supplied\n"));
1265 }
1266
1267#undef IS_OPTION_PRESENT
1268#undef APPEND_NOT_PRESENT_OPTION
1269 return a_rRetOpts;
1270}
1271
1272
1273
1274/*********************************************************************************************************************************
1275* Group Condition Matching *
1276*********************************************************************************************************************************/
1277
1278bool GroupConfig::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1279 const OptUserClassId &a_ridUserClass) const
1280{
1281 /*
1282 * Check the inclusive ones first, only one need to match.
1283 */
1284 for (GroupConditionVec::const_iterator itIncl = m_Inclusive.begin(); itIncl != m_Inclusive.end(); ++itIncl)
1285 if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1286 {
1287 /*
1288 * Now make sure it isn't excluded by any of the exclusion condition.
1289 */
1290 for (GroupConditionVec::const_iterator itExcl = m_Exclusive.begin(); itExcl != m_Exclusive.end(); ++itExcl)
1291 if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
1292 return false;
1293 return true;
1294 }
1295
1296 return false;
1297}
1298
1299
1300int GroupCondition::initCondition(const char *a_pszValue, bool a_fInclusive)
1301{
1302 m_fInclusive = a_fInclusive;
1303 return m_strValue.assignNoThrow(a_pszValue);
1304}
1305
1306
1307bool GroupCondition::matchClassId(bool a_fPresent, const std::vector<uint8_t> &a_rBytes, bool fWildcard) const RT_NOEXCEPT
1308{
1309 if (a_fPresent)
1310 {
1311 size_t const cbBytes = a_rBytes.size();
1312 if (cbBytes > 0)
1313 {
1314 if (a_rBytes[cbBytes - 1] == '\0')
1315 {
1316 uint8_t const *pb = &a_rBytes.front();
1317 if (!fWildcard)
1318 return m_strValue.equals((const char *)pb);
1319 return RTStrSimplePatternMatch(m_strValue.c_str(), (const char *)pb);
1320 }
1321
1322 if (cbBytes <= 255)
1323 {
1324 char szTmp[256];
1325 memcpy(szTmp, &a_rBytes.front(), cbBytes);
1326 szTmp[cbBytes] = '\0';
1327 if (!fWildcard)
1328 return m_strValue.equals(szTmp);
1329 return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
1330 }
1331 }
1332 }
1333 return false;
1334
1335}
1336
1337
1338int GroupConditionMAC::initCondition(const char *a_pszValue, bool a_fInclusive)
1339{
1340 int vrc = RTNetStrToMacAddr(a_pszValue, &m_MACAddress);
1341 if (RT_SUCCESS(vrc))
1342 return GroupCondition::initCondition(a_pszValue, a_fInclusive);
1343 return vrc;
1344}
1345
1346
1347bool GroupConditionMAC::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1348 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1349{
1350 RT_NOREF(a_ridVendorClass, a_ridUserClass);
1351 return a_ridClient.mac() == m_MACAddress;
1352}
1353
1354
1355bool GroupConditionMACWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1356 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1357{
1358 RT_NOREF(a_ridVendorClass, a_ridUserClass);
1359 char szTmp[32];
1360 RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &a_ridClient.mac());
1361 return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
1362}
1363
1364
1365bool GroupConditionVendorClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1366 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1367{
1368 RT_NOREF(a_ridClient, a_ridUserClass);
1369 return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value());
1370}
1371
1372
1373bool GroupConditionVendorClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1374 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1375{
1376 RT_NOREF(a_ridClient, a_ridUserClass);
1377 return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value(), true /*fWildcard*/);
1378}
1379
1380
1381bool GroupConditionUserClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1382 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1383{
1384 RT_NOREF(a_ridClient, a_ridVendorClass);
1385 return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value());
1386}
1387
1388
1389bool GroupConditionUserClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
1390 const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT
1391{
1392 RT_NOREF(a_ridClient, a_ridVendorClass);
1393 return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value(), true /*fWildcard*/);
1394}
1395
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette