From 02ef0350af6bb2ee1aacb6e7e4ec9acb21eb3dc7 Mon Sep 17 00:00:00 2001 From: Dmitry Galkin Date: Thu, 22 Mar 2018 17:53:27 +0000 Subject: [PATCH] Check TXT/SPF records for RFC1035 sec. 5.1 If record data has empty spaces it should be surrounded by double quotes. This patch will raise an error during validation if record has whitespaces, empty spaces, tabs, etc., but not wrapped in " " (double quotes). Corresponding RFC part: is expressed in one or two ways: as a contiguous set of characters without interior spaces, or as a string beginning with a " and ending with a ". Inside a " delimited string any character can occur, except for a " itself, which must be quoted using \ (back slash). Closes-Bug: 1755788 Depends-On: https://review.openstack.org/#/c/617809/ Change-Id: I159d0732688ddf1337ab3602a84a43fd043dcaa2 --- designate/objects/recordset.py | 6 ++-- designate/objects/rrdata_spf.py | 18 ++++++++++ designate/objects/rrdata_txt.py | 18 ++++++++++ .../unit/test_objects/test_rrdata_spf.py | 33 +++++++++++++++++++ .../unit/test_objects/test_rrdata_txt.py | 33 +++++++++++++++++++ ...8-txt-spf-validation-d18e43c12691132a.yaml | 7 ++++ 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 designate/tests/unit/test_objects/test_rrdata_spf.py create mode 100644 designate/tests/unit/test_objects/test_rrdata_txt.py create mode 100644 releasenotes/notes/bug-1755788-txt-spf-validation-d18e43c12691132a.yaml diff --git a/designate/objects/recordset.py b/designate/objects/recordset.py index 807293d26..42b067e44 100755 --- a/designate/objects/recordset.py +++ b/designate/objects/recordset.py @@ -187,10 +187,8 @@ def validate(self): error_indexes.append(i) except Exception as e: - error_message = str.format( - 'Provided object is not valid. ' - 'Got a %s error with message %s' % - (type(e).__name__, six.text_type(e))) + error_message = ('Provided object is not valid. Got a %s error' + ' with message %s' % (type(e).__name__, six.text_type(e))) raise exceptions.InvalidObject(error_message) else: diff --git a/designate/objects/rrdata_spf.py b/designate/objects/rrdata_spf.py index 1202bc77f..7fc1be3c2 100644 --- a/designate/objects/rrdata_spf.py +++ b/designate/objects/rrdata_spf.py @@ -16,6 +16,7 @@ from designate.objects.record import RecordList from designate.objects import base from designate.objects import fields +from designate.exceptions import InvalidObject @base.DesignateRegistry.register @@ -32,6 +33,23 @@ def _to_string(self): return self.txt_data def _from_string(self, value): + if (not value.startswith('"') and not value.endswith('"')): + # value with spaces should be quoted as per RFC1035 5.1 + for element in value: + if element.isspace(): + err = ("Empty spaces are not allowed in SPF record, " + "unless wrapped in double quotes.") + raise InvalidObject(err) + else: + # quotes within value should be escaped with backslash + strip_value = value.strip('"') + for index, char in enumerate(strip_value): + if char == '"': + if strip_value[index - 1] != "\\": + err = ("Quotation marks should be escaped with " + "backslash.") + raise InvalidObject(err) + self.txt_data = value # The record type is defined in the RFC. This will be used when the record diff --git a/designate/objects/rrdata_txt.py b/designate/objects/rrdata_txt.py index d22581f8a..44e13fff5 100644 --- a/designate/objects/rrdata_txt.py +++ b/designate/objects/rrdata_txt.py @@ -16,6 +16,7 @@ from designate.objects.record import RecordList from designate.objects import base from designate.objects import fields +from designate.exceptions import InvalidObject @base.DesignateRegistry.register @@ -32,6 +33,23 @@ def _to_string(self): return self.txt_data def _from_string(self, value): + if (not value.startswith('"') and not value.endswith('"')): + # value with spaces should be quoted as per RFC1035 5.1 + for element in value: + if element.isspace(): + err = ("Empty spaces are not allowed in TXT record, " + "unless wrapped in double quotes.") + raise InvalidObject(err) + else: + # quotes within value should be escaped with backslash + strip_value = value.strip('"') + for index, char in enumerate(strip_value): + if char == '"': + if strip_value[index - 1] != "\\": + err = ("Quotation marks should be escaped with " + "backslash.") + raise InvalidObject(err) + self.txt_data = value # The record type is defined in the RFC. This will be used when the record diff --git a/designate/tests/unit/test_objects/test_rrdata_spf.py b/designate/tests/unit/test_objects/test_rrdata_spf.py new file mode 100644 index 000000000..f11f05efa --- /dev/null +++ b/designate/tests/unit/test_objects/test_rrdata_spf.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from oslo_log import log as logging +import oslotest.base +import testtools + +from designate import exceptions + +from designate import objects + +LOG = logging.getLogger(__name__) + + +class RRDataSPFTest(oslotest.base.BaseTestCase): + + def test_reject_non_quoted_spaces(self): + record = objects.SPF(data='foo bar') + with testtools.ExpectedException(exceptions.InvalidObject): + record.validate() + + def test_reject_non_escaped_quotes(self): + record = objects.SPF(data='foo"bar') + with testtools.ExpectedException(exceptions.InvalidObject): + record.validate() diff --git a/designate/tests/unit/test_objects/test_rrdata_txt.py b/designate/tests/unit/test_objects/test_rrdata_txt.py new file mode 100644 index 000000000..f6ac899b1 --- /dev/null +++ b/designate/tests/unit/test_objects/test_rrdata_txt.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from oslo_log import log as logging +import oslotest.base +import testtools + +from designate import exceptions + +from designate import objects + +LOG = logging.getLogger(__name__) + + +class RRDataTXTTest(oslotest.base.BaseTestCase): + + def test_reject_non_quoted_spaces(self): + record = objects.TXT(data='foo bar') + with testtools.ExpectedException(exceptions.InvalidObject): + record.validate() + + def test_reject_non_escaped_quotes(self): + record = objects.TXT(data='foo"bar') + with testtools.ExpectedException(exceptions.InvalidObject): + record.validate() diff --git a/releasenotes/notes/bug-1755788-txt-spf-validation-d18e43c12691132a.yaml b/releasenotes/notes/bug-1755788-txt-spf-validation-d18e43c12691132a.yaml new file mode 100644 index 000000000..d5574be50 --- /dev/null +++ b/releasenotes/notes/bug-1755788-txt-spf-validation-d18e43c12691132a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + TXT and SPF records are now validated for empty spaces in the values. + If record value has empty space it should use "" quotation according to + RFC-1035 section 5.1. Use of single quotation mark within record value + requires quote symbol to be escaped with backslash. Bug-1755788