LCOV - code coverage report
Current view: top level - DCPS/security/AccessControl - XmlUtils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 52 145 35.9 %
Date: 2023-04-30 01:32:43 Functions: 6 11 54.5 %

          Line data    Source code
       1             : /*
       2             :  * Distributed under the OpenDDS License.
       3             :  * See: http://www.OpenDDS.org/license.html
       4             :  */
       5             : 
       6             : #include "XmlUtils.h"
       7             : 
       8             : #include <dds/DCPS/debug.h>
       9             : 
      10             : #include <ace/OS_NS_strings.h>
      11             : #include <ace/OS_NS_time.h>
      12             : 
      13             : #include <xercesc/framework/MemBufInputSource.hpp>
      14             : #include <xercesc/sax/ErrorHandler.hpp>
      15             : #include <xercesc/util/XMLDateTime.hpp>
      16             : #include <xercesc/util/PlatformUtils.hpp>
      17             : #include <xercesc/util/XercesVersion.hpp>
      18             : 
      19             : #include <stdexcept>
      20             : 
      21             : OPENDDS_BEGIN_VERSIONED_NAMESPACE_DECL
      22             : 
      23             : namespace OpenDDS {
      24             : namespace Security {
      25             : namespace XmlUtils {
      26             : 
      27             : using DDS::Security::DomainId_t;
      28             : using OpenDDS::DCPS::security_debug;
      29             : 
      30           0 : std::string to_string(const xercesc::SAXParseException& ex)
      31             : {
      32             :   return
      33           0 :     to_string(ex.getSystemId()) +
      34           0 :     ":" + DCPS::to_dds_string(ex.getLineNumber()) +
      35           0 :     ":" + DCPS::to_dds_string(ex.getColumnNumber()) +
      36           0 :     ": " + to_string(ex.getMessage());
      37             : }
      38             : 
      39             : namespace {
      40             :   class ErrorHandler : public xercesc::ErrorHandler {
      41             :   public:
      42           0 :     void warning(const xercesc::SAXParseException& ex)
      43             :     {
      44           0 :       if (security_debug.access_warn) {
      45           0 :         ACE_ERROR((LM_ERROR, "(%P|%t) WARNING: {access_warn} "
      46             :           "XmlUtils::ErrorHandler: %C\n",
      47             :           to_string(ex).c_str()));
      48             :       }
      49           0 :     }
      50             : 
      51           0 :     void error(const xercesc::SAXParseException& ex)
      52             :     {
      53           0 :       if (security_debug.access_error) {
      54           0 :         ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} "
      55             :           "XmlUtils::ErrorHandler: %C\n",
      56             :           to_string(ex).c_str()));
      57             :       }
      58           0 :     }
      59             : 
      60           0 :     void fatalError(const xercesc::SAXParseException& ex)
      61             :     {
      62           0 :       error(ex);
      63           0 :     }
      64             : 
      65           0 :     void resetErrors()
      66             :     {
      67           0 :     }
      68             :   };
      69             : }
      70             : 
      71          50 : bool get_parser(ParserPtr& parser, const std::string& filename, const std::string& xml)
      72             : {
      73             :   try {
      74          50 :     xercesc::XMLPlatformUtils::Initialize();
      75             : 
      76           0 :   } catch (const xercesc::XMLException& ex) {
      77           0 :     if (security_debug.access_error) {
      78           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
      79             :         "XMLPlatformUtils::Initialize XMLException: %C\n",
      80             :         to_string(ex).c_str()));
      81             :     }
      82           0 :     parser.reset();
      83           0 :     return false;
      84           0 :   }
      85             : 
      86          50 :   parser.reset(new xercesc::XercesDOMParser());
      87             : 
      88          50 :   parser->setDoNamespaces(true);
      89          50 :   parser->setIncludeIgnorableWhitespace(false);
      90          50 :   parser->setCreateCommentNodes(false);
      91             : 
      92          50 :   ErrorHandler error_handler;
      93          50 :   parser->setErrorHandler(&error_handler);
      94             : 
      95             :   xercesc::MemBufInputSource contentbuf(
      96          50 :     reinterpret_cast<const XMLByte*>(xml.c_str()), xml.size(), filename.c_str());
      97             : 
      98             :   try {
      99          50 :     parser->parse(contentbuf);
     100             : 
     101           0 :   } catch (const xercesc::XMLException& ex) {
     102           0 :     if (security_debug.access_error) {
     103           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
     104             :         "XMLException while parsing \"%C\": %C\n",
     105             :         filename.c_str(), to_string(ex).c_str()));
     106             :     }
     107           0 :     parser.reset();
     108           0 :     return false;
     109             : 
     110           0 :   } catch (const xercesc::DOMException& ex) {
     111           0 :     if (security_debug.access_error) {
     112           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
     113             :         "DOMException while parsing \"%C\": %C\n",
     114             :         filename.c_str(), to_string(ex).c_str()));
     115             :     }
     116           0 :     parser.reset();
     117           0 :     return false;
     118             : 
     119           0 :   } catch (...) {
     120           0 :     if (security_debug.access_error) {
     121           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
     122             :         "Unexpected exception while parsing \"%C\"",
     123             :         filename.c_str()));
     124             :     }
     125           0 :     parser.reset();
     126           0 :     return false;
     127           0 :   }
     128             : 
     129          50 :   if (!parser->getDocument()->getDocumentElement()) {
     130           0 :     if (security_debug.access_error) {
     131           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
     132             :         "XML document \"%C\" is empty\n",
     133             :         filename.c_str()));
     134             :     }
     135           0 :     parser.reset();
     136           0 :     return false;
     137             :   }
     138             : 
     139          50 :   return true;
     140          50 : }
     141             : 
     142        1097 : std::string to_string(const XMLCh* in)
     143             : {
     144        1097 :   char* c = xercesc::XMLString::transcode(in);
     145        1097 :   const std::string s(c);
     146        1097 :   xercesc::XMLString::release(&c);
     147        2194 :   return s;
     148           0 : }
     149             : 
     150         188 : bool parse_bool(const XMLCh* in, bool& value)
     151             : {
     152             :   /*
     153             :    * The security spec specifies the XML schema spec's boolean type, but the
     154             :    * XML schema spec doesn't specify that true and false can be capitalized
     155             :    * like the security spec uses.
     156             :    */
     157         188 :   const std::string s = to_string(in);
     158         188 :   if (!ACE_OS::strcasecmp(s.c_str(), "true") || s == "1") {
     159          85 :     value = true;
     160         103 :   } else if (!ACE_OS::strcasecmp(s.c_str(), "false") || s == "0") {
     161         103 :     value = false;
     162             :   } else {
     163           0 :     return false;
     164             :   }
     165         188 :   return true;
     166         188 : }
     167             : 
     168             : #if _XERCES_VERSION >= 30200
     169             : #  define XMLDATETIME_HAS_GETEPOCH 1
     170             : #else
     171             : #  define XMLDATETIME_HAS_GETEPOCH 0
     172             : namespace {
     173             :   bool parse_time_string(
     174             :     const std::string& whole, size_t& pos, std::string& value, size_t width = std::string::npos)
     175             :   {
     176             :     const bool to_end = width == std::string::npos;
     177             :     const size_t size = whole.size();
     178             :     if (pos >= size || (!to_end && pos + width > size)) {
     179             :       return false;
     180             :     }
     181             :     value = whole.substr(pos, width);
     182             :     pos = to_end ? size : pos + width;
     183             :     return true;
     184             :   }
     185             : 
     186             :   bool parse_time_field(
     187             :     const std::string& whole, size_t& pos, int& value, size_t width = 2)
     188             :   {
     189             :     std::string string;
     190             :     if (parse_time_string(whole, pos, string, width)) {
     191             :       if (OpenDDS::DCPS::convertToInteger(string, value) && string[0] != '-') {
     192             :         return true;
     193             :       }
     194             :     }
     195             :     if (security_debug.access_error) {
     196             :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_field: "
     197             :         "failed to get field at pos %B in \"%C\"\n",
     198             :         pos, whole.c_str()));
     199             :     }
     200             :     return false;
     201             :   }
     202             : 
     203             :   bool parse_time_char_or_end(
     204             :     const std::string& whole, size_t& pos, const std::string& choices, char& c, bool& end)
     205             :   {
     206             :     std::string string;
     207             :     if (parse_time_string(whole, pos, string, 1)) {
     208             :       end = false;
     209             :       c = string[0];
     210             :       if (choices.find(c) != std::string::npos) {
     211             :         return true;
     212             :       }
     213             :       if (security_debug.access_error) {
     214             :         ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_char_or_end: "
     215             :           "failed to find one of \"%C\" at pos %B in \"%C\"\n",
     216             :           choices.c_str(), pos, whole.c_str()));
     217             :       }
     218             :       return false;
     219             :     }
     220             :     end = true;
     221             :     return true;
     222             :   }
     223             : 
     224             :   bool parse_time_char(
     225             :     const std::string& whole, size_t& pos, const std::string& choices)
     226             :   {
     227             :     char c;
     228             :     bool end;
     229             :     if (!parse_time_char_or_end(whole, pos, choices, c, end)) {
     230             :       return false;
     231             :     }
     232             :     if (end) {
     233             :       if (security_debug.access_error) {
     234             :         ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_char: "
     235             :           "failed to find one of \"%C\" at end of \"%C\"\n",
     236             :           choices.c_str(), whole.c_str()));
     237             :       }
     238             :       return false;
     239             :     }
     240             :     return true;
     241             :   }
     242             : }
     243             : #endif // XMLDATETIME_HAS_GETEPOCH
     244             : 
     245          56 : bool parse_time(const XMLCh* in, time_t& value)
     246             : {
     247          56 :   xercesc::XMLDateTime xdt(in);
     248             :   try {
     249          56 :     xdt.parseDateTime();
     250           0 :   } catch (const xercesc::XMLException& ex) {
     251           0 :     if (security_debug.access_error) {
     252           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
     253             :         "failed to parse date/time \"%C\": %C\n",
     254             :         to_string(in).c_str(), to_string(ex).c_str()));
     255             :     }
     256           0 :     return false;
     257           0 :   }
     258             : 
     259             : #if XMLDATETIME_HAS_GETEPOCH
     260          56 :   value = xdt.getEpoch();
     261             : #else
     262             :   /*
     263             :    * Doesn't seem like older Xerces' actually have a way to get the information
     264             :    * from XMLDateTime, so we have to do it ourselves.
     265             :    *
     266             :    * For this we'll follow https://www.w3.org/TR/xmlschema-2/#dateTime, which
     267             :    * is basically the same as ISO8601.
     268             :    * The exceptions are:
     269             :    * - We won't accept a negative datetime (-0001 is the year 2BCE), since the
     270             :    *   standard library might not be able to handle such a far back and we're
     271             :    *   certainly not expecting it.
     272             :    * - The Schema spec says that times without timezone info should be
     273             :    *   interpreted as "local", but seems it be intentionally vague as what that
     274             :    *   means. The DDS Security 1.1 spec explicitly says in 9.4.1.3.2.2 that it
     275             :    *   would be UTC. Xerces getEpoch values match up with this in the unit test
     276             :    *   for this code, so it's also interpreting them as UTC instead of
     277             :    *   something like the local system timezone.
     278             :    * - Since we use time_t we will accept but ignore fractional seconds.
     279             :    *   NOTE: The security spec is missing fractional seconds in a format
     280             :    *   description in a comment in example XML. This comment has been copied
     281             :    *   into many permissions files sitting in OpenDDS.
     282             :    */
     283             : 
     284             :   const std::string str = to_string(in);
     285             :   if (str[0] == '-') {
     286             :     if (security_debug.access_error) {
     287             :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
     288             :         "date/time can't be negative: \"%C\"\n",
     289             :         str.c_str()));
     290             :     }
     291             :     return false;
     292             :   }
     293             : 
     294             :   /*
     295             :    * After this the expected lexical format given by the Schema spec is:
     296             :    *   yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? ((('+' | '-') hh ':' mm) | 'Z')?
     297             :    * or alternatively as the Security spec says, except with fractional seconds:
     298             :    *   CCYY-MM-DDThh:mm:ss[.fffff...][Z|(+|-)hh:mm]
     299             :    * See unit tests for this code for examples
     300             :    */
     301             :   std::tm dttm;
     302             :   size_t pos = 0;
     303             : 
     304             :   // Year
     305             :   if (!parse_time_field(str, pos, dttm.tm_year, 4)) {
     306             :     return false;
     307             :   }
     308             :   dttm.tm_year -= 1900;
     309             :   if (!parse_time_char(str, pos, "-")) {
     310             :     return false;
     311             :   }
     312             :   // Month
     313             :   if (!parse_time_field(str, pos, dttm.tm_mon)) {
     314             :     return false;
     315             :   }
     316             :   --dttm.tm_mon;
     317             :   if (!parse_time_char(str, pos, "-")) {
     318             :     return false;
     319             :   }
     320             :   // Day
     321             :   if (!parse_time_field(str, pos, dttm.tm_mday)) {
     322             :     return false;
     323             :   }
     324             :   if (!parse_time_char(str, pos, "T")) {
     325             :     return false;
     326             :   }
     327             :   // Hour
     328             :   if (!parse_time_field(str, pos, dttm.tm_hour)) {
     329             :     return false;
     330             :   }
     331             :   if (!parse_time_char(str, pos, ":")) {
     332             :     return false;
     333             :   }
     334             :   // Minutes
     335             :   if (!parse_time_field(str, pos, dttm.tm_min)) {
     336             :     return false;
     337             :   }
     338             :   if (!parse_time_char(str, pos, ":")) {
     339             :     return false;
     340             :   }
     341             :   // Seconds
     342             :   if (!parse_time_field(str, pos, dttm.tm_sec)) {
     343             :     return false;
     344             :   }
     345             : 
     346             :   // Ignore Fractional Seconds
     347             :   if (str[pos] == '.') {
     348             :     ++pos;
     349             :     for (; str[pos] >= '0' && str[pos] <= '9'; ++pos) {
     350             :     }
     351             :   }
     352             : 
     353             :   // Optional Timezone Info
     354             :   time_t timezone_offset = 0;
     355             :   char tz_char = '\0';
     356             :   bool end;
     357             :   if (!parse_time_char_or_end(str, pos, "+-Z", tz_char, end)) {
     358             :     return false;
     359             :   }
     360             :   if (!end && tz_char != 'Z') {
     361             :     int tz_hour;
     362             :     if (!parse_time_field(str, pos, tz_hour)) {
     363             :       return false;
     364             :     }
     365             : 
     366             :     if (!parse_time_char(str, pos, ":")) {
     367             :       return false;
     368             :     }
     369             : 
     370             :     int tz_min;
     371             :     if (!parse_time_field(str, pos, tz_min)) {
     372             :       return false;
     373             :     }
     374             : 
     375             :     /*
     376             :      * We will have to do the reverse of the sign to convert the local time to
     377             :      * UTC. For example 00:00:00-06:00 (12AM CST) is 06:00:00+00:00 (6AM UTC).
     378             :      */
     379             :     const int timezone_offset_sign = tz_char == '+' ? -1 : 1;
     380             : 
     381             :     /*
     382             :      * We could modify the tm struct here to account for the timezone, but then
     383             :      * we'd also have to adjust the hour if the minutes carried over and the
     384             :      * date fields if the hour carried over to another date. Instead we're
     385             :      * going to calculate the number of seconds in the offset and add that to
     386             :      * the time_t value later.
     387             :      */
     388             :     timezone_offset = timezone_offset_sign * (tz_hour * 60 * 60 + tz_min * 60);
     389             :   }
     390             : 
     391             :   std::string leftover;
     392             :   if (parse_time_string(str, pos, leftover)) {
     393             :     if (security_debug.access_error) {
     394             :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
     395             :         "leftover characters in \"%C\": \"%C\"\n",
     396             :         str.c_str(), leftover.c_str()));
     397             :     }
     398             :     return false;
     399             :   }
     400             : 
     401             :   dttm.tm_isdst = 0; // Don't try to do anything with DST
     402             :   value = std::mktime(&dttm);
     403             :   if (value == time_t(-1)) {
     404             :     if (security_debug.access_error) {
     405             :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
     406             :         "failed to convert to time_t \"%C\"\n",
     407             :         to_string(in).c_str()));
     408             :     }
     409             :     return false;
     410             :   }
     411             : 
     412             :   // mktime assumes the tm struct is in the local time, so we need to correct
     413             :   // for that.
     414             :   const time_t local_timezone = static_cast<time_t>(ace_timezone());
     415             :   if (local_timezone == 0 && errno == ENOTSUP) {
     416             :     if (security_debug.access_error) {
     417             :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
     418             :         "ace_timezone not supported\n"));
     419             :     }
     420             :     return false;
     421             :   }
     422             :   value -= local_timezone;
     423             : 
     424             :   // Adjust for the timezone specified in the string if there was one.
     425             :   value += timezone_offset;
     426             : #endif // XMLDATETIME_HAS_GETEPOCH
     427             : 
     428          56 :   return value;
     429          56 : }
     430             : 
     431             : namespace {
     432          50 :   bool parse_domain_id(const xercesc::DOMNode* node, DomainId_t& value)
     433             :   {
     434             :     // NOTE: DomainId_t is a signed type, but a domain id actually can't be a negative value.
     435          50 :     unsigned int i = 0;
     436         100 :     const bool success = xercesc::XMLString::textToBin(node->getTextContent(), i) &&
     437          50 :       i <= static_cast<unsigned>(domain_id_max);
     438          50 :     if (success) {
     439          50 :       value = static_cast<DomainId_t>(i);
     440             :     }
     441          50 :     return success;
     442             :   }
     443             : }
     444             : 
     445          50 : bool parse_domain_id_set(const xercesc::DOMNode* node, Security::DomainIdSet& domain_id_set)
     446             : {
     447          50 :   const xercesc::DOMNodeList* const domainIdNodes = node->getChildNodes();
     448         200 :   for (XMLSize_t did = 0, did_len = domainIdNodes->getLength(); did < did_len; ++did) {
     449         150 :     const xercesc::DOMNode* const domainIdNode = domainIdNodes->item(did);
     450         150 :     if (!is_element(domainIdNode)) {
     451         100 :       continue;
     452             :     }
     453          50 :     const std::string domainIdNodeName = to_string(domainIdNode->getNodeName());
     454          50 :     if (domainIdNodeName == "id") {
     455             :       DomainId_t domain_id;
     456          50 :       if (!parse_domain_id(domainIdNode, domain_id)) {
     457           0 :         if (security_debug.access_error) {
     458           0 :           ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     459             :             "Invalid domain ID \"%C\" in id\n",
     460             :             to_string(domainIdNode).c_str()));
     461             :         }
     462           0 :         return false;
     463             :       }
     464          50 :       domain_id_set.add(domain_id);
     465             : 
     466           0 :     } else if (domainIdNodeName == "id_range") {
     467           0 :       const xercesc::DOMNodeList* const domRangeIdNodes = domainIdNode->getChildNodes();
     468           0 :       DomainId_t min_value = domain_id_min;
     469           0 :       bool has_min = false;
     470           0 :       DomainId_t max_value = domain_id_max;
     471           0 :       bool has_max = false;
     472           0 :       const XMLSize_t drid_len = domRangeIdNodes->getLength();
     473           0 :       for (XMLSize_t drid = 0; drid < drid_len; ++drid) {
     474           0 :         const xercesc::DOMNode* const domRangeIdNode = domRangeIdNodes->item(drid);
     475           0 :         if (!is_element(domRangeIdNode)) {
     476           0 :           continue;
     477             :         }
     478           0 :         const std::string domRangeIdNodeName = to_string(domRangeIdNode->getNodeName());
     479           0 :         if ("min" == domRangeIdNodeName && !has_min) {
     480           0 :           if (!parse_domain_id(domRangeIdNode, min_value)) {
     481           0 :             if (security_debug.access_error) {
     482           0 :               ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     483             :                 "Invalid domain ID \"%C\" in min_value\n",
     484             :                 to_string(domRangeIdNode).c_str()));
     485             :             }
     486           0 :             return false;
     487             :           }
     488           0 :           has_min = true;
     489             : 
     490           0 :         } else if ("max" == domRangeIdNodeName && !has_max) {
     491           0 :           if (!parse_domain_id(domRangeIdNode, max_value)) {
     492           0 :             if (security_debug.access_error) {
     493           0 :               ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     494             :                 "Invalid domain ID \"%C\" in max_value\n",
     495             :                 to_string(domRangeIdNode).c_str()));
     496             :             }
     497           0 :             return false;
     498             :           }
     499           0 :           if (min_value > max_value) {
     500           0 :             if (security_debug.access_error) {
     501           0 :               ACE_ERROR((LM_ERROR,"(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     502             :                 "Permission XML Domain Range invalid.\n"));
     503             :             }
     504           0 :             return false;
     505             :           }
     506           0 :           has_max = true;
     507             : 
     508             :         } else {
     509           0 :           if (security_debug.access_error) {
     510           0 :             ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     511             :               "Invalid tag \"%C\" in id_range\n",
     512             :               domRangeIdNodeName.c_str()));
     513             :           }
     514           0 :           return false;
     515             :         }
     516           0 :       }
     517             : 
     518           0 :       domain_id_set.add(min_value, max_value);
     519             : 
     520             :     } else {
     521           0 :       if (security_debug.access_error) {
     522           0 :         ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     523             :           "Invalid tag \"%C\" in domain ID set\n",
     524             :           domainIdNodeName.c_str()));
     525             :       }
     526           0 :       return false;
     527             :     }
     528          50 :   }
     529             : 
     530          50 :   if (domain_id_set.empty()) {
     531           0 :     if (security_debug.access_error) {
     532           0 :       ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
     533             :         "empty domain ID set\n"));
     534             :     }
     535           0 :     return false;
     536             :   }
     537             : 
     538          50 :   return true;
     539             : }
     540             : 
     541             : } // namespace XmlUtils
     542             : } // namespace Security
     543             : } // namespace OpenDDS
     544             : 
     545             : OPENDDS_END_VERSIONED_NAMESPACE_DECL

Generated by: LCOV version 1.16