| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/dns/nsswitch_reader.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| namespace { |
| |
| class TestFileReader { |
| public: |
| explicit TestFileReader(std::string text) : text_(std::move(text)) {} |
| TestFileReader(const TestFileReader&) = delete; |
| TestFileReader& operator=(const TestFileReader&) = delete; |
| |
| NsswitchReader::FileReadCall GetFileReadCall() { |
| return base::BindRepeating(&TestFileReader::ReadFile, |
| base::Unretained(this)); |
| } |
| |
| std::string ReadFile() { |
| CHECK(!already_read_); |
| |
| already_read_ = true; |
| return text_; |
| } |
| |
| private: |
| std::string text_; |
| bool already_read_ = false; |
| }; |
| |
| class NsswitchReaderTest : public testing::Test { |
| public: |
| NsswitchReaderTest() = default; |
| NsswitchReaderTest(const NsswitchReaderTest&) = delete; |
| NsswitchReaderTest& operator=(const NsswitchReaderTest&) = delete; |
| |
| protected: |
| NsswitchReader reader_; |
| }; |
| |
| // Attempt to load the actual nsswitch.conf for the test machine and run |
| // rationality checks for the result. |
| TEST_F(NsswitchReaderTest, ActualReadAndParseHosts) { |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| // Assume nobody will ever run this on a machine with more than 1000 |
| // configured services. |
| EXPECT_THAT(services, testing::SizeIs(testing::Le(1000u))); |
| |
| // Assume no service will ever have more than 10 configured actions per |
| // service. |
| for (const NsswitchReader::ServiceSpecification& service : services) { |
| EXPECT_THAT(service.actions, testing::SizeIs(testing::Le(10u))); |
| } |
| } |
| |
| TEST_F(NsswitchReaderTest, FileReadErrorResultsInDefault) { |
| TestFileReader file_reader(""); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| // Expect "files dns". |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns))); |
| } |
| |
| TEST_F(NsswitchReaderTest, MissingHostsResultsInDefault) { |
| const std::string kFile = |
| "passwd: files ldap\nshadow: files\ngroup: files ldap\n"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| // Expect "files dns". |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesAllKnownServices) { |
| const std::string kFile = |
| "hosts: files dns mdns mdns4 mdns6 mdns_minimal mdns4_minimal " |
| "mdns6_minimal myhostname resolve nis"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns4), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns6), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdnsMinimal), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdns4Minimal), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdns6Minimal), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMyHostname), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kResolve), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kNis))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesRepeatedServices) { |
| const std::string kFile = "hosts: mdns4 mdns6 mdns6 myhostname"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns4), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns6), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns6), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMyHostname))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesAllKnownActions) { |
| const std::string kFile = |
| "hosts: files [UNAVAIL=RETURN] [UNAVAIL=CONTINUE] [UNAVAIL=MERGE]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kFiles, |
| {{/*negated=*/false, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kContinue}, |
| {/*negated=*/false, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kMerge}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesAllKnownStatuses) { |
| const std::string kFile = |
| "hosts: dns [SUCCESS=RETURN] [NOTFOUND=RETURN] [UNAVAIL=RETURN] " |
| "[TRYAGAIN=RETURN]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kDns, |
| {{/*negated=*/false, NsswitchReader::Status::kSuccess, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kNotFound, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kTryAgain, |
| NsswitchReader::Action::kReturn}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesRepeatedActions) { |
| const std::string kFile = |
| "hosts: nis [!SUCCESS=RETURN] [NOTFOUND=RETURN] [NOTFOUND=RETURN] " |
| "[!UNAVAIL=RETURN]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kNis, |
| {{/*negated=*/true, NsswitchReader::Status::kSuccess, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kNotFound, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kNotFound, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/true, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kReturn}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, HandlesAtypicalWhitespace) { |
| const std::string kFile = |
| " database: service \n\n hosts: files\tdns mdns4 \t mdns6 \t " |
| "\t\n\t\n"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns4), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdns6))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesActionsWithoutService) { |
| const std::string kFile = "hosts: [SUCCESS=RETURN]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kUnknown, |
| {{/*negated=*/false, NsswitchReader::Status::kSuccess, |
| NsswitchReader::Action::kReturn}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesNegatedActions) { |
| const std::string kFile = |
| "hosts: mdns_minimal [!UNAVAIL=RETURN] [NOTFOUND=CONTINUE] " |
| "[!TRYAGAIN=CONTINUE]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdnsMinimal, |
| {{/*negated=*/true, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kReturn}, |
| {/*negated=*/false, NsswitchReader::Status::kNotFound, |
| NsswitchReader::Action::kContinue}, |
| {/*negated=*/true, NsswitchReader::Status::kTryAgain, |
| NsswitchReader::Action::kContinue}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesUnrecognizedServiceAsUnknown) { |
| const std::string kFile = |
| "passwd: files\nhosts: files super_awesome_service myhostname"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kUnknown), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMyHostname))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesUnrecognizedStatusAsUnknown) { |
| const std::string kFile = |
| "hosts: nis [HELLO=CONTINUE]\nshadow: service\ndatabase: cheese"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kNis, |
| {{/*negated=*/false, NsswitchReader::Status::kUnknown, |
| NsswitchReader::Action::kContinue}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesUnrecognizedActionAsUnknown) { |
| const std::string kFile = |
| "more: service\nhosts: mdns6 [!UNAVAIL=HI]\nshadow: service"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdns6, |
| {{/*negated=*/true, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kUnknown}}))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesInvalidActionsAsUnknown) { |
| const std::string kFile = "hosts: mdns_minimal [a=b=c] nis"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdnsMinimal, |
| {{/*negated=*/false, NsswitchReader::Status::kUnknown, |
| NsswitchReader::Action::kUnknown}}), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kNis))); |
| } |
| |
| TEST_F(NsswitchReaderTest, ParsesInvalidlyClosedActionsAsUnknown) { |
| const std::string kFile = "hosts: resolve [SUCCESS=CONTINUE dns"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kResolve, |
| {{/*negated=*/false, NsswitchReader::Status::kUnknown, |
| NsswitchReader::Action::kUnknown}}), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns))); |
| } |
| |
| TEST_F(NsswitchReaderTest, IgnoresComments) { |
| const std::string kFile = |
| "#hosts: files super_awesome_service myhostname\nnetmask: service"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| // Expect "files dns" due to not finding an uncommented "hosts:" row. |
| EXPECT_THAT( |
| services, |
| testing::ElementsAre( |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns))); |
| } |
| |
| TEST_F(NsswitchReaderTest, IgnoresEndOfLineComments) { |
| const std::string kFile = |
| "hosts: files super_awesome_service myhostname # dns"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kFiles), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kUnknown), |
| NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMyHostname))); |
| } |
| |
| TEST_F(NsswitchReaderTest, IgnoresCapitalization) { |
| const std::string kFile = "HoStS: mDNS6 [!uNaVaIl=MeRgE]"; |
| TestFileReader file_reader(kFile); |
| reader_.set_file_read_call_for_testing(file_reader.GetFileReadCall()); |
| |
| std::vector<NsswitchReader::ServiceSpecification> services = |
| reader_.ReadAndParseHosts(); |
| |
| EXPECT_THAT(services, |
| testing::ElementsAre(NsswitchReader::ServiceSpecification( |
| NsswitchReader::Service::kMdns6, |
| {{/*negated=*/true, NsswitchReader::Status::kUnavailable, |
| NsswitchReader::Action::kMerge}}))); |
| } |
| |
| } // namespace |
| } // namespace net |