}
void
-AttrDefs::parseLine(const string& line) {
+AttrDefs::parseLine(const string& line, unsigned int depth) {
// Ignore empty lines.
if (line.empty()) {
return;
if (tokens.empty()) {
return;
}
+ // $INCLUDE include.
+ if (tokens[0] == "$INCLUDE") {
+ if (tokens.size() != 2) {
+ isc_throw(Unexpected, "expected 2 tokens, got " << tokens.size());
+ }
+ readDictionary(tokens[1], depth + 1);
+ return;
+ }
// Attribute definition.
if (tokens[0] == "ATTRIBUTE") {
if (tokens.size() != 4) {
}
void
-AttrDefs::readDictionary(const string& path) {
+AttrDefs::readDictionary(const string& path, unsigned depth) {
+ if (depth >= 5) {
+ isc_throw(BadValue, "Too many nested $INCLUDE");
+ }
ifstream ifs(path);
if (!ifs.is_open()) {
isc_throw(BadValue, "can't open dictionary '" << path << "': "
isc_throw(BadValue, "bad dictionary '" << path << "'");
}
try {
- readDictionary(ifs);
+ readDictionary(ifs, depth);
ifs.close();
} catch (const exception& ex) {
ifs.close();
- isc_throw(BadValue, ex.what() << " in dictionary '" << path << "'");
+ isc_throw(BadValue, ex.what() << " in dictionary '" << path << "'"
+ << (depth > 0 ? "," : ""));
}
}
void
-AttrDefs::readDictionary(istream& is) {
+AttrDefs::readDictionary(istream& is, unsigned int depth) {
size_t lines = 0;
string line;
try {
while (is.good()) {
++lines;
getline(is, line);
- parseLine(line);
+ parseLine(line, depth);
}
if (!is.eof()) {
isc_throw(BadValue, "I/O error: " << strerror(errno));
/// @brief Read a dictionary from a file.
///
/// Fills attribute and integer constant definition tables from
- /// a dictionary file.
+ /// a dictionary file. Recursion depth is initialized to 0,
+ /// incremented by includes and limited to 5.
///
/// @param path dictionary file path.
- void readDictionary(const std::string& path);
+ /// @param depth recursion depth.
+ void readDictionary(const std::string& path, unsigned int depth = 0);
/// @brief Read a dictionary from an input stream.
///
/// Fills attribute and integer constant definition tables from
- /// a dictionary input stream.
+ /// a dictionary input stream. Recursion depth is initialized to 0,
+ /// incremented by includes and limited to 5.
///
/// @param is input stream.
- void readDictionary(std::istream& is);
+ /// @param depth recursion depth.
+ void readDictionary(std::istream& is, unsigned int depth = 0);
/// @brief Check if a list of standard attribute definitions
/// are available and correct.
/// @brief Parse a dictionary line.
///
/// @param line line to parse.
- void parseLine(const std::string& line);
+ /// @param depth recursion depth.
+ void parseLine(const std::string& line, unsigned int depth);
/// @brief Attribute definition container.
AttrDefContainer container_;
/// @brief Destructor.
virtual ~DictionaryTest() {
AttrDefs::instance().clear();
+ static_cast<void>(remove(TEST_DICT));
}
/// @brief Parse a line.
///
/// @param line line to parse.
- void parseLine(const string& line) {
+ /// @param depth recursion depth.
+ void parseLine(const string& line, unsigned int depth = 0) {
istringstream is(line + "\n");
- AttrDefs::instance().readDictionary(is);
+ AttrDefs::instance().readDictionary(is, depth);
}
/// @brief Parse a list of lines.
///
/// @param lines list of lines.
- void parseLines(const list<string>& lines) {
+ /// @param depth recursion depth.
+ void parseLines(const list<string>& lines, unsigned int depth = 0) {
string content;
for (auto const& line : lines) {
content += line + "\n";
}
istringstream is(content);
- AttrDefs::instance().readDictionary(is);
+ AttrDefs::instance().readDictionary(is, depth);
}
+
+ /// @brief writes specified content to a file.
+ ///
+ /// @param file_name name of file to be written.
+ /// @param content content to be written to file.
+ void writeFile(const std::string& file_name, const std::string& content) {
+ static_cast<void>(remove(file_name.c_str()));
+ ofstream out(file_name.c_str(), ios::trunc);
+ EXPECT_TRUE(out.is_open());
+ out << content;
+ out.close();
+ }
+
+ /// Name of a dictionary file used during tests.
+ static const char* TEST_DICT;
};
+const char* DictionaryTest::TEST_DICT = "test-dict";
+
// Verifies standards definitions can be read from the dictionary.
TEST_F(DictionaryTest, standard) {
ASSERT_NO_THROW_LOG(AttrDefs::instance().readDictionary(TEST_DICTIONARY));
checkStandardDefs(RadiusConfigParser::USED_STANDARD_ATTR_DEFS));
}
+// Verifies the $INCLUDE entry.
+TEST_F(DictionaryTest, include) {
+ list<string> include;
+ include.push_back("# Including the dictonary");
+ include.push_back(string("$INCLUDE ") + string(TEST_DICTIONARY));
+ include.push_back("# Dictionary included");
+ // include.push_back("VALUE Vendor-Specific ISC 2495");
+ include.push_back("VALUE ARAP-Security ISC 2495");
+ EXPECT_NO_THROW_LOG(parseLines(include));
+ EXPECT_NO_THROW_LOG(AttrDefs::instance().
+ checkStandardDefs(RadiusConfigParser::USED_STANDARD_ATTR_DEFS));
+ // auto isc = AttrDefs::instance().getByName(PW_VENDOR_SPECIFIC, "ISC");
+ auto isc = AttrDefs::instance().getByName(PW_ARAP_SECURITY, "ISC");
+ ASSERT_TRUE(isc);
+ EXPECT_EQ(2495, isc->value_);
+
+ // max depth is 5.
+ EXPECT_THROW_MSG(parseLines(include, 4), BadValue,
+ "Too many nested $INCLUDE at line 2");
+}
+
+// Verifies the $INCLUDE entry can't eat the stack.
+TEST_F(DictionaryTest, includeLimit) {
+ string include = "$INCLUDE " + string(TEST_DICT) + "\n";
+ writeFile(TEST_DICT, include);
+ string expected = "Too many nested $INCLUDE ";
+ expected += "at line 1 in dictionary 'test-dict', ";
+ expected += "at line 1 in dictionary 'test-dict', ";
+ expected += "at line 1 in dictionary 'test-dict', ";
+ expected += "at line 1 in dictionary 'test-dict', ";
+ expected += "at line 1";
+ EXPECT_THROW_MSG(parseLine(string("$INCLUDE ") + string(TEST_DICT)),
+ BadValue, expected);
+}
+
namespace {
// RAII device freeing the glob buffer when going out of scope.