Commit 2e80ea06 authored by Mukund Sivaraman's avatar Mukund Sivaraman
Browse files

[2512] Add support for the CAA RR type

parent 312e27c0
......@@ -76,6 +76,8 @@ EXTRA_DIST += rdata/generic/minfo_14.cc
EXTRA_DIST += rdata/generic/minfo_14.h
EXTRA_DIST += rdata/generic/afsdb_18.cc
EXTRA_DIST += rdata/generic/afsdb_18.h
EXTRA_DIST += rdata/generic/caa_257.cc
EXTRA_DIST += rdata/generic/caa_257.h
EXTRA_DIST += rdata/hs_4/a_1.cc
EXTRA_DIST += rdata/hs_4/a_1.h
EXTRA_DIST += rdata/in_1/a_1.cc
......
......@@ -43,7 +43,7 @@ meta_types = {
'27': 'gpos', '29': 'loc', '36': 'kx', '37': 'cert', '42': 'apl',
'45': 'ipseckey', '52': 'tlsa', '55': 'hip', '103': 'unspec',
'104': 'nid', '105': 'l32', '106': 'l64', '107': 'lp', '249': 'tkey',
'253': 'mailb', '256': 'uri', '257': 'caa'
'253': 'mailb', '256': 'uri'
}
# Classes that don't have any known types. This is a dict from type code
# values (as string) to textual mnemonic.
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdata/generic/detail/char_string.h>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
struct CAAImpl {
// straightforward representation of CAA RDATA fields
CAAImpl(uint8_t flags, const std::string& tag,
const std::vector<uint8_t>& value) :
flags_(flags),
tag_(tag),
value_(value)
{}
uint8_t flags_;
const std::string tag_;
// The first byte of this vector contains the length of the rest of
// the vector. This byte is actually unused and is skipped when
// reading the vector.
const detail::CharString value_;
};
// helper function for string and lexer constructors
CAAImpl*
CAA::constructFromLexer(MasterLexer& lexer) {
const uint32_t flags =
lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (flags > 255) {
isc_throw(InvalidRdataText,
"CAA flags field out of range");
}
// Tag field must not be empty.
const std::string tag =
lexer.getNextToken(MasterToken::STRING).getString();
if (tag.empty()) {
isc_throw(InvalidRdataText, "CAA tag field is empty");
} else if (tag.size() > 255) {
isc_throw(InvalidRdataText,
"CAA tag field is too large: " << tag.size());
}
// Value field may be empty.
std::vector<uint8_t> value;
MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
if ((token.getType() != MasterToken::END_OF_FILE) &&
(token.getType() != MasterToken::END_OF_LINE))
{
detail::stringToCharString(token.getStringRegion(), value);
} else {
// Convert it into a CharString.
value.push_back(0);
}
return (new CAAImpl(flags, tag, value));
}
/// \brief Constructor from string.
///
/// The given string must represent a valid CAA RDATA. There can be
/// extra space characters at the beginning or end of the text (which
/// are simply ignored), but other extra text, including a new line,
/// will make the construction fail with an exception.
///
/// The Flags, Tag and Value fields must be within their valid ranges,
/// but are not constrained to the values defined in RFC6844. The Tag
/// field must not be empty.
///
/// \throw InvalidRdataText if any fields are missing, out of their
/// valid ranges, incorrect, or empty.
///
/// \param caa_str A string containing the RDATA to be created
CAA::CAA(const string& caa_str) :
impl_(NULL)
{
// We use auto_ptr here because if there is an exception in this
// constructor, the destructor is not called and there could be a
// leak of the CAAImpl that constructFromLexer() returns.
std::auto_ptr<CAAImpl> impl_ptr(NULL);
try {
std::istringstream ss(caa_str);
MasterLexer lexer;
lexer.pushSource(ss);
impl_ptr.reset(constructFromLexer(lexer));
if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
isc_throw(InvalidRdataText, "extra input text for CAA: "
<< caa_str);
}
} catch (const MasterLexer::LexerError& ex) {
isc_throw(InvalidRdataText, "Failed to construct CAA from '" <<
caa_str << "': " << ex.what());
}
impl_ = impl_ptr.release();
}
/// \brief Constructor with a context of MasterLexer.
///
/// The \c lexer should point to the beginning of valid textual
/// representation of an CAA RDATA.
///
/// \throw MasterLexer::LexerError General parsing error such as missing
/// field.
/// \throw InvalidRdataText Fields are out of their valid ranges,
/// incorrect, or empty.
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
CAA::CAA(MasterLexer& lexer, const Name*,
MasterLoader::Options, MasterLoaderCallbacks&) :
impl_(constructFromLexer(lexer))
{
}
/// \brief Constructor from InputBuffer.
///
/// The passed buffer must contain a valid CAA RDATA.
///
/// The Flags, Tag and Value fields must be within their valid ranges,
/// but are not constrained to the values defined in RFC6844. The Tag
/// field must not be empty.
CAA::CAA(InputBuffer& buffer, size_t rdata_len) {
if (rdata_len < 2) {
isc_throw(InvalidRdataLength, "CAA record too short");
}
const uint8_t flags = buffer.readUint8();
const uint8_t tag_length = buffer.readUint8();
rdata_len -= 2;
if (tag_length == 0) {
isc_throw(InvalidRdataText, "CAA tag field is empty");
}
if (rdata_len < tag_length) {
isc_throw(InvalidRdataLength,
"RDATA is too short for CAA tag field");
}
vector<uint8_t> tag_vec;
tag_vec.resize(tag_length + 1);
tag_vec[0] = tag_length;
buffer.readData(&tag_vec[1], tag_length);
rdata_len -= tag_length;
const std::string tag = detail::charStringToString(tag_vec);
if (rdata_len > 255) {
isc_throw(InvalidRdataLength,
"CAA value field is too long: " << rdata_len);
}
vector<uint8_t> value;
value.resize(rdata_len + 1);
value[0] = rdata_len;
if (rdata_len > 0) {
buffer.readData(&value[1], rdata_len);
}
impl_ = new CAAImpl(flags, tag, value);
}
CAA::CAA(uint8_t flags, const std::string& tag, const std::string& value) :
impl_(NULL)
{
if (tag.empty()) {
isc_throw(isc::InvalidParameter,
"CAA tag field is empty");
} else if (tag.size() > 255) {
isc_throw(isc::InvalidParameter,
"CAA tag field is too large: " << tag.size());
}
if (value.size() > 255) {
isc_throw(isc::InvalidParameter,
"CAA value field is too long: " << value.size());
}
std::vector<uint8_t> value_vec;
value_vec.reserve(value.size() + 1);
value_vec.push_back(value.size());
value_vec.insert(value_vec.end(), value.begin(), value.end());
impl_ = new CAAImpl(flags, tag, value_vec);
}
CAA::CAA(const CAA& other) :
Rdata(), impl_(new CAAImpl(*other.impl_))
{}
CAA&
CAA::operator=(const CAA& source) {
if (this == &source) {
return (*this);
}
CAAImpl* newimpl = new CAAImpl(*source.impl_);
delete impl_;
impl_ = newimpl;
return (*this);
}
CAA::~CAA() {
delete impl_;
}
void
CAA::toWire(OutputBuffer& buffer) const {
buffer.writeUint8(impl_->flags_);
buffer.writeUint8(impl_->tag_.size());
if (!impl_->tag_.empty()) {
buffer.writeData(&impl_->tag_[0],
impl_->tag_.size());
}
if (impl_->value_.size() > 1) {
buffer.writeData(&impl_->value_[1],
impl_->value_.size() - 1);
}
}
void
CAA::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeUint8(impl_->flags_);
renderer.writeUint8(impl_->tag_.size());
if (!impl_->tag_.empty()) {
renderer.writeData(&impl_->tag_[0],
impl_->tag_.size());
}
if (impl_->value_.size() > 1) {
renderer.writeData(&impl_->value_[1],
impl_->value_.size() - 1);
}
}
std::string
CAA::toText() const {
std::string result;
result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
result += " " + impl_->tag_;
result += " \"" + detail::charStringToString(impl_->value_) + "\"";
return (result);
}
int
CAA::compare(const Rdata& other) const {
const CAA& other_caa = dynamic_cast<const CAA&>(other);
if (impl_->flags_ < other_caa.impl_->flags_) {
return (-1);
} else if (impl_->flags_ > other_caa.impl_->flags_) {
return (1);
}
// Do a case-insensitive compare of the tag strings.
const int result = boost::ilexicographical_compare
<std::string, std::string>(impl_->tag_, other_caa.impl_->tag_);
if (result != 0) {
return (result);
}
return (detail::compareCharStrings(impl_->value_,
other_caa.impl_->value_));
}
uint8_t
CAA::getFlags() const {
return (impl_->flags_);
}
const std::string&
CAA::getTag() const {
return (impl_->tag_);
}
// END_RDATA_NAMESPACE
// END_ISC_NAMESPACE
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// BEGIN_HEADER_GUARD
#include <stdint.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <string>
#include <vector>
// BEGIN_ISC_NAMESPACE
// BEGIN_COMMON_DECLARATIONS
// END_COMMON_DECLARATIONS
// BEGIN_RDATA_NAMESPACE
struct CAAImpl;
class CAA : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
CAA(uint8_t flags, const std::string& tag, const std::string& value);
CAA& operator=(const CAA& source);
~CAA();
///
/// Specialized methods
///
uint8_t getFlags() const;
const std::string& getTag() const;
private:
CAAImpl* constructFromLexer(MasterLexer& lexer);
CAAImpl* impl_;
};
// END_RDATA_NAMESPACE
// END_ISC_NAMESPACE
// END_HEADER_GUARD
// Local Variables:
// mode: c++
// End:
......@@ -63,6 +63,7 @@ run_unittests_SOURCES += rdata_minfo_unittest.cc
run_unittests_SOURCES += rdata_tsig_unittest.cc
run_unittests_SOURCES += rdata_naptr_unittest.cc
run_unittests_SOURCES += rdata_hinfo_unittest.cc
run_unittests_SOURCES += rdata_caa_unittest.cc
run_unittests_SOURCES += rrset_unittest.cc
run_unittests_SOURCES += question_unittest.cc
run_unittests_SOURCES += rrparamregistry_unittest.cc
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <algorithm>
#include <string>
#include <util/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
#include <dns/tests/rdata_unittest.h>
#include <boost/algorithm/string.hpp>
using isc::UnitTestUtil;
using namespace std;
using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
class Rdata_CAA_Test : public RdataTest {
protected:
Rdata_CAA_Test() :
caa_txt("0 issue \"ca.example.net\""),
rdata_caa(caa_txt)
{}
void checkFromText_None(const string& rdata_str) {
checkFromText<generic::CAA, isc::Exception, isc::Exception>(
rdata_str, rdata_caa, false, false);
}
void checkFromText_InvalidText(const string& rdata_str) {
checkFromText<generic::CAA, InvalidRdataText, InvalidRdataText>(
rdata_str, rdata_caa, true, true);
}
void checkFromText_LexerError(const string& rdata_str) {
checkFromText
<generic::CAA, InvalidRdataText, MasterLexer::LexerError>(
rdata_str, rdata_caa, true, true);
}
void checkFromText_BadString(const string& rdata_str) {
checkFromText
<generic::CAA, InvalidRdataText, isc::Exception>(
rdata_str, rdata_caa, true, false);
}
const string caa_txt;
const generic::CAA rdata_caa;
};
const uint8_t rdata_caa_wiredata[] = {
// flags
0x00,
// tag length
0x5,
// tag
'i', 's', 's', 'u', 'e',
// value
'c', 'a', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
'.', 'n', 'e', 't'
};
TEST_F(Rdata_CAA_Test, createFromText) {
// Basic test
checkFromText_None(caa_txt);
// With different spacing
checkFromText_None("0 issue \"ca.example.net\"");
// Combination of lowercase and uppercase
checkFromText_None("0 IssUE \"ca.example.net\"");
// string constructor throws if there's extra text,
// but lexer constructor doesn't
checkFromText_BadString(caa_txt + "\n" + caa_txt);
// Missing value field
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
}
TEST_F(Rdata_CAA_Test, fields) {
// Some of these may not be RFC conformant, but we relax the check
// in our code to work with other field values that may show up in
// the future.
EXPECT_NO_THROW(const generic::CAA rdata_caa2("1 issue \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("2 issue \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("3 issue \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("128 issue \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("255 issue \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 foo \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 bar \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 12345 \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 w0x1y2z3 \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 relaxed-too \"ca.example.net\""));
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 RELAXED.too \"ca.example.net\""));
// No value
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
// > 255 would be broken
EXPECT_THROW(const generic::CAA rdata_caa2("256 issue \"ca.example.net\""),
InvalidRdataText);
// Missing tag actually passes because it parses the value as tag
// and assumes that the value is empty instead.
EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 \"ca.example.net\""));
// Tag is too long
const std::string tag(256, 'a');
const std::string rdata_txt("0 " + tag + " \"ca.example.net\"");
EXPECT_THROW(const generic::CAA rdata_caa2(rdata_txt), InvalidRdataText);
}
TEST_F(Rdata_CAA_Test, badText) {
checkFromText_LexerError("0");
checkFromText_LexerError("ZERO issue \"ca.example.net\"");
EXPECT_THROW(const generic::CAA rdata_caa2(caa_txt + " extra text"),
InvalidRdataText);
// Yes, this is redundant to the last test cases in the .fields test
checkFromText_InvalidText("2345 issue \"ca.example.net\"");
// negative values are trapped in the lexer rather than the
// constructor
checkFromText_LexerError("-2 issue \"ca.example.net\"");
}
TEST_F(Rdata_CAA_Test, copyAndAssign) {
// Copy construct
generic::CAA rdata_caa2(rdata_caa);
EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
// Assignment, mainly to confirm it doesn't cause disruption.
rdata_caa2 = rdata_caa;
EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
}
TEST_F(Rdata_CAA_Test, createFromWire) {
// Basic test
EXPECT_EQ(0, rdata_caa.compare(
*rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire1.wire")));
// Combination of lowercase and uppercase
EXPECT_EQ(0, rdata_caa.compare(
*rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire2.wire")));
// Value field is empty
EXPECT_NO_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire3.wire"));
// Tag field is empty
EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire4.wire"),
InvalidRdataText);
// Value field is shorter than rdata len
EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire5"),
InvalidBufferPosition);
// all RDATA is missing
EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
"rdata_caa_fromWire6"),
InvalidBufferPosition);
}
TEST_F(Rdata_CAA_Test, createFromParams) {
const generic::CAA rdata_caa2(0, "issue", "ca.example.net");
EXPECT_EQ(0, rdata_caa2.compare(rdata_caa));
// Tag is empty
EXPECT_THROW(const generic::CAA rdata_caa3(0, "", "ca.example.net"),
isc::InvalidParameter);
// Tag is too long
const std::string tag(256, 'a');
EXPECT_THROW(const generic::CAA rdata_caa3(0, tag, "ca.example.net"),
isc::InvalidParameter);
// Value is too long