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
|