VirtualBox

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

Last change on this file since 93072 was 87694, checked in by vboxsync, 4 years ago

Dhcpd: Config::i_read - fix typo in comment. NB: the comment seems to
be false b/c, we set up release log right before we call i_read().
bugref:9288

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