Index: /trunk/src/VBox/NetworkServices/Dhcpd/Config.cpp
===================================================================
--- /trunk/src/VBox/NetworkServices/Dhcpd/Config.cpp	(revision 79809)
+++ /trunk/src/VBox/NetworkServices/Dhcpd/Config.cpp	(revision 79810)
@@ -65,5 +65,5 @@
         va_list va;
         va_start(va, a_pszMsgFmt);
-        m_strMsg.appendPrintf(a_pszMsgFmt, va);
+        m_strMsg.appendPrintfV(a_pszMsgFmt, va);
         va_end(va);
     }
@@ -91,15 +91,15 @@
             m_strMsg.append('/');
             m_strMsg.append(pNode->getName());
-            if (pNode->isElement())
+            if (pNode->isElement() && pNode->getParent())
             {
                 xml::ElementNode const *pElm = (xml::ElementNode const *)pNode;
-                for (xml::Node const *pNodeChild = pElm->getFirstChild(); pNodeChild != NULL;
-                    pNodeChild = pNodeChild->getNextSibiling())
-                    if (pNodeChild->isAttribute())
+                for (xml::Node const *pAttrib = pElm->getFirstAttribute(); pAttrib != NULL;
+                      pAttrib = pAttrib->getNextSibiling())
+                    if (pAttrib->isAttribute())
                     {
                         m_strMsg.append("[@");
-                        m_strMsg.append(pNodeChild->getName());
+                        m_strMsg.append(pAttrib->getName());
                         m_strMsg.append('=');
-                        m_strMsg.append(pNodeChild->getValue());
+                        m_strMsg.append(pAttrib->getValue());
                         m_strMsg.append(']');
                     }
@@ -882,20 +882,65 @@
 void GroupConfig::i_parseChild(const xml::ElementNode *pElmChild, bool fStrict)
 {
+    /*
+     * Match the condition
+     */
+    std::unique_ptr<GroupCondition> ptrCondition;
     if (pElmChild->nameEquals("ConditionMAC"))
-    { }
+        ptrCondition.reset(new GroupConditionMAC());
     else if (pElmChild->nameEquals("ConditionMACWildcard"))
-    { }
+        ptrCondition.reset(new GroupConditionMACWildcard());
     else if (pElmChild->nameEquals("ConditionVendorClassID"))
-    { }
+        ptrCondition.reset(new GroupConditionVendorClassID());
     else if (pElmChild->nameEquals("ConditionVendorClassIDWildcard"))
-    { }
+        ptrCondition.reset(new GroupConditionVendorClassIDWildcard());
     else if (pElmChild->nameEquals("ConditionUserClassID"))
-    { }
+        ptrCondition.reset(new GroupConditionUserClassID());
     else if (pElmChild->nameEquals("ConditionUserClassIDWildcard"))
-    { }
+        ptrCondition.reset(new GroupConditionUserClassIDWildcard());
     else
     {
+        /*
+         * Not a condition, pass it on to the base class.
+         */
         ConfigLevelBase::i_parseChild(pElmChild, fStrict);
         return;
+    }
+
+    /*
+     * Get the attributes and initialize the condition.
+     */
+    bool fInclusive;
+    if (!pElmChild->getAttributeValue("inclusive", fInclusive))
+        fInclusive = true;
+    const char *pszValue = pElmChild->findAttributeValue("value");
+    if (pszValue && *pszValue)
+    {
+        int rc = ptrCondition->initCondition(pszValue, fInclusive);
+        if (RT_SUCCESS(rc))
+        {
+            /*
+             * Add it to the appropriate vector.
+             */
+            if (fInclusive)
+                m_Inclusive.push_back(ptrCondition.release());
+            else
+                m_Exclusive.push_back(ptrCondition.release());
+        }
+        else
+        {
+            ConfigFileError Xcpt(pElmChild, "initCondition failed with %Rrc for '%s' and %RTbool", rc, pszValue, fInclusive);
+            if (!fStrict)
+                LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
+            else
+                throw ConfigFileError(Xcpt);
+        }
+    }
+    else
+    {
+        ConfigFileError Xcpt(pElmChild, "condition value is empty or missing (inclusive=%RTbool)", fInclusive);
+        if (!fStrict)
+            LogRelFunc(("%s, ignoring condition\n", Xcpt.what()));
+        else
+            throw Xcpt;
     }
 }
@@ -990,5 +1035,7 @@
 
     /* Groups: */
-    RT_NOREF(a_ridVendorClass, a_ridUserClass); /* not yet */
+    for (GroupConfigMap::const_iterator itGrp = m_GroupConfigs.begin(); itGrp != m_GroupConfigs.end(); ++itGrp)
+        if (itGrp->second->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+            a_rRetConfigs.push_back(itGrp->second);
 
     /* Global: */
@@ -1076,2 +1123,127 @@
     return a_rRetOpts;
 }
+
+
+
+/*********************************************************************************************************************************
+*   Group Condition Matching                                                                                                     *
+*********************************************************************************************************************************/
+
+bool GroupConfig::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                        const OptUserClassId &a_ridUserClass) const
+{
+    /*
+     * Check the inclusive ones first, only one need to match.
+     */
+    for (GroupConditionVec::const_iterator itIncl = m_Inclusive.begin(); itIncl != m_Inclusive.end(); ++itIncl)
+        if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+        {
+            /*
+             * Now make sure it isn't excluded by any of the exclusion condition.
+             */
+            for (GroupConditionVec::const_iterator itExcl = m_Exclusive.begin(); itExcl != m_Exclusive.end(); ++itExcl)
+                if ((*itIncl)->match(a_ridClient, a_ridVendorClass, a_ridUserClass))
+                    return false;
+            return true;
+        }
+
+    return false;
+}
+
+
+int GroupCondition::initCondition(const char *a_pszValue, bool a_fInclusive)
+{
+    m_fInclusive = a_fInclusive;
+    return m_strValue.assignNoThrow(a_pszValue);
+}
+
+
+bool GroupCondition::matchClassId(bool a_fPresent, const std::vector<uint8_t> &a_rBytes, bool fWildcard) const RT_NOEXCEPT
+{
+    if (a_fPresent)
+    {
+        size_t const cbBytes = a_rBytes.size();
+        if (cbBytes > 0)
+        {
+            if (a_rBytes[cbBytes - 1] == '\0')
+            {
+                uint8_t const *pb = &a_rBytes.front();
+                if (!fWildcard)
+                    return m_strValue.equals((const char *)pb);
+                return RTStrSimplePatternMatch(m_strValue.c_str(), (const char *)pb);
+            }
+
+            if (cbBytes <= 255)
+            {
+                char szTmp[256];
+                memcpy(szTmp, &a_rBytes.front(), cbBytes);
+                szTmp[cbBytes] = '\0';
+                if (!fWildcard)
+                    return m_strValue.equals(szTmp);
+                return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
+            }
+        }
+    }
+    return false;
+
+}
+
+
+int GroupConditionMAC::initCondition(const char *a_pszValue, bool a_fInclusive)
+{
+    int vrc = RTNetStrToMacAddr(a_pszValue, &m_MACAddress);
+    if (RT_SUCCESS(vrc))
+        return GroupCondition::initCondition(a_pszValue, a_fInclusive);
+    return vrc;
+}
+
+
+bool GroupConditionMAC::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                              const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridVendorClass, a_ridUserClass);
+    return a_ridClient.mac() == m_MACAddress;
+}
+
+
+bool GroupConditionMACWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                                      const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridVendorClass, a_ridUserClass);
+    char szTmp[32];
+    RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &a_ridClient.mac());
+    return RTStrSimplePatternMatch(m_strValue.c_str(), szTmp);
+}
+
+
+bool GroupConditionVendorClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                                        const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridClient, a_ridUserClass);
+    return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value());
+}
+
+
+bool GroupConditionVendorClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                                                const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridClient, a_ridUserClass);
+    return matchClassId(a_ridVendorClass.present(), a_ridVendorClass.value(), true /*fWildcard*/);
+}
+
+
+bool GroupConditionUserClassID::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                                      const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridClient, a_ridVendorClass);
+    return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value());
+}
+
+
+bool GroupConditionUserClassIDWildcard::match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                                              const OptUserClassId &a_ridUserClass) const
+{
+    RT_NOREF(a_ridClient, a_ridVendorClass);
+    return matchClassId(a_ridVendorClass.present(), a_ridUserClass.value(), true /*fWildcard*/);
+}
+
Index: /trunk/src/VBox/NetworkServices/Dhcpd/Config.h
===================================================================
--- /trunk/src/VBox/NetworkServices/Dhcpd/Config.h	(revision 79809)
+++ /trunk/src/VBox/NetworkServices/Dhcpd/Config.h	(revision 79810)
@@ -97,18 +97,83 @@
 
 
-#if 0 /* later */
 /**
  * Group membership condition.
  */
-class GroupConditionBase
+class GroupCondition
 {
 protected:
     /** The value. */
     RTCString   m_strValue;
-
-public:
-
-};
-#endif
+    /** Inclusive (true) or exclusive (false), latter takes precedency. */
+    bool        m_fInclusive;
+
+public:
+    virtual ~GroupCondition()
+    {}
+
+    virtual int  initCondition(const char *a_pszValue, bool a_fInclusive);
+    virtual bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+                       const OptUserClassId &a_ridUserClass) const RT_NOEXCEPT = 0;
+
+    /** @name accessors
+     * @{  */
+    RTCString const    &getValue()     const RT_NOEXCEPT { return m_strValue; }
+    bool                getInclusive() const RT_NOEXCEPT { return m_fInclusive; }
+    /** @} */
+
+protected:
+    bool matchClassId(bool a_fPresent, std::vector<uint8_t> const &a_rBytes, bool fWildcard = false) const RT_NOEXCEPT;
+};
+
+/** MAC condition. */
+class GroupConditionMAC : public GroupCondition
+{
+private:
+    RTMAC   m_MACAddress;
+public:
+    int  initCondition(const char *a_pszValue, bool a_fInclusive) RT_OVERRIDE;
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
+
+/** MAC wildcard condition. */
+class GroupConditionMACWildcard : public GroupCondition
+{
+public:
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
+
+/** Vendor class ID condition. */
+class GroupConditionVendorClassID : public GroupCondition
+{
+public:
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
+
+/** Vendor class ID wildcard condition. */
+class GroupConditionVendorClassIDWildcard : public GroupCondition
+{
+public:
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
+
+/** User class ID condition. */
+class GroupConditionUserClassID : public GroupCondition
+{
+public:
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
+
+/** User class ID wildcard condition. */
+class GroupConditionUserClassIDWildcard : public GroupCondition
+{
+public:
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass,
+               const OptUserClassId &a_ridUserClass) const RT_OVERRIDE;
+};
 
 
@@ -118,7 +183,13 @@
 class GroupConfig : public ConfigLevelBase
 {
-public:
+private:
+    typedef std::vector<GroupCondition *> GroupConditionVec;
+
     /** The group name. */
-    RTCString       m_strName;
+    RTCString           m_strName;
+    /** Vector containing the inclusive membership conditions (must match one). */
+    GroupConditionVec   m_Inclusive;
+    /** Vector containing the exclusive membership conditions (must match none). */
+    GroupConditionVec   m_Exclusive;
 
 public:
@@ -129,9 +200,10 @@
 
     void initFromXml(xml::ElementNode const *pElm, bool fStrict) RT_OVERRIDE;
-    const char *getType() const RT_NOEXCEPT RT_OVERRIDE { return "group"; }
-    const char *getName() const RT_NOEXCEPT RT_OVERRIDE { return m_strName.c_str(); }
+    bool match(const ClientId &a_ridClient, const OptVendorClassId &a_ridVendorClass, const OptUserClassId &a_ridUserClass) const;
 
     /** @name Accessors
      * @{ */
+    const char         *getType() const RT_NOEXCEPT RT_OVERRIDE { return "group"; }
+    const char         *getName() const RT_NOEXCEPT RT_OVERRIDE { return m_strName.c_str(); }
     RTCString const    &getGroupName() const RT_NOEXCEPT        { return m_strName; }
     /** @} */
Index: /trunk/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp
===================================================================
--- /trunk/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp	(revision 79809)
+++ /trunk/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp	(revision 79810)
@@ -24,4 +24,5 @@
 #include "DhcpOptions.h"
 
+#include <iprt/ctype.h>
 #include <iprt/string.h>
 
@@ -279,5 +280,4 @@
     }
 
-    bool fHeader = true;
     for (rawopts_t::const_iterator it = m_rawopts.begin(); it != m_rawopts.end(); ++it)
     {
@@ -294,15 +294,21 @@
 
             default:
-                if (fHeader)
-                {
-                    LogRel((" other options:"));
-                    fHeader = false;
-                }
-                LogRel((" %d", optcode));
-                break;
+            {
+                size_t const   cbBytes = it->second.size();
+                uint8_t const *pbBytes = &it->second.front();
+                bool fAllPrintable = true;
+                for (size_t off = 0; off < cbBytes; off++)
+                    if (!RT_C_IS_PRINT((char )pbBytes[off]))
+                    {
+                        fAllPrintable = false;
+                        break;
+                    }
+                if (fAllPrintable)
+                    LogRel(("  %2d: '%.*s'\n", optcode, cbBytes, pbBytes));
+                else
+                    LogRel(("  %2d: %.*Rhxs\n", optcode, cbBytes, pbBytes));
+            }
         }
     }
-    if (!fHeader)
-        LogRel(("\n"));
 }
 
