• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/dns/nsswitch_reader.h"
6 
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "build/build_config.h"
20 
21 #if BUILDFLAG(IS_POSIX)
22 #include <netdb.h>
23 #endif  // defined (OS_POSIX)
24 
25 namespace net {
26 
27 namespace {
28 
29 #ifdef _PATH_NSSWITCH_CONF
30 constexpr base::FilePath::CharType kNsswitchPath[] =
31     FILE_PATH_LITERAL(_PATH_NSSWITCH_CONF);
32 #else
33 constexpr base::FilePath::CharType kNsswitchPath[] =
34     FILE_PATH_LITERAL("/etc/nsswitch.conf");
35 #endif
36 
37 // Choose 1 MiB as the largest handled filesize. Arbitrarily chosen as seeming
38 // large enough to handle any reasonable file contents and similar to the size
39 // limit for HOSTS files (32 MiB).
40 constexpr size_t kMaxFileSize = 1024 * 1024;
41 
ReadNsswitch()42 std::string ReadNsswitch() {
43   std::string file;
44   bool result = base::ReadFileToStringWithMaxSize(base::FilePath(kNsswitchPath),
45                                                   &file, kMaxFileSize);
46   UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.Read",
47                         result || file.size() == kMaxFileSize);
48   UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.TooLarge",
49                         !result && file.size() == kMaxFileSize);
50 
51   if (result)
52     return file;
53 
54   return "";
55 }
56 
SkipRestOfLine(base::StringPiece text)57 base::StringPiece SkipRestOfLine(base::StringPiece text) {
58   base::StringPiece::size_type line_end = text.find('\n');
59   if (line_end == base::StringPiece::npos)
60     return "";
61   return text.substr(line_end);
62 }
63 
64 // In case of multiple entries for `database_name`, finds only the first.
FindDatabase(base::StringPiece text,base::StringPiece database_name)65 base::StringPiece FindDatabase(base::StringPiece text,
66                                base::StringPiece database_name) {
67   DCHECK(!text.empty());
68   DCHECK(!database_name.empty());
69   DCHECK(!base::StartsWith(database_name, "#"));
70   DCHECK(!base::IsAsciiWhitespace(database_name.front()));
71   DCHECK(base::EndsWith(database_name, ":"));
72 
73   while (!text.empty()) {
74     text = base::TrimWhitespaceASCII(text, base::TrimPositions::TRIM_LEADING);
75 
76     if (base::StartsWith(text, database_name,
77                          base::CompareCase::INSENSITIVE_ASCII)) {
78       DCHECK(!base::StartsWith(text, "#"));
79 
80       text = text.substr(database_name.size());
81       base::StringPiece::size_type line_end = text.find('\n');
82       if (line_end != base::StringPiece::npos)
83         text = text.substr(0, line_end);
84 
85       return base::TrimWhitespaceASCII(text, base::TrimPositions::TRIM_ALL);
86     }
87 
88     text = SkipRestOfLine(text);
89   }
90 
91   return "";
92 }
93 
TokenizeAction(base::StringPiece action_column)94 NsswitchReader::ServiceAction TokenizeAction(base::StringPiece action_column) {
95   DCHECK(!action_column.empty());
96   DCHECK_EQ(action_column.find(']'), base::StringPiece::npos);
97   DCHECK_EQ(action_column.find_first_of(base::kWhitespaceASCII),
98             base::StringPiece::npos);
99 
100   NsswitchReader::ServiceAction result = {/*negated=*/false,
101                                           NsswitchReader::Status::kUnknown,
102                                           NsswitchReader::Action::kUnknown};
103 
104   std::vector<base::StringPiece> split = base::SplitStringPiece(
105       action_column, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
106   if (split.size() != 2)
107     return result;
108 
109   if (split[0].size() >= 2 && split[0].front() == '!') {
110     result.negated = true;
111     split[0] = split[0].substr(1);
112   }
113 
114   if (base::EqualsCaseInsensitiveASCII(split[0], "SUCCESS")) {
115     result.status = NsswitchReader::Status::kSuccess;
116   } else if (base::EqualsCaseInsensitiveASCII(split[0], "NOTFOUND")) {
117     result.status = NsswitchReader::Status::kNotFound;
118   } else if (base::EqualsCaseInsensitiveASCII(split[0], "UNAVAIL")) {
119     result.status = NsswitchReader::Status::kUnavailable;
120   } else if (base::EqualsCaseInsensitiveASCII(split[0], "TRYAGAIN")) {
121     result.status = NsswitchReader::Status::kTryAgain;
122   }
123 
124   if (base::EqualsCaseInsensitiveASCII(split[1], "RETURN")) {
125     result.action = NsswitchReader::Action::kReturn;
126   } else if (base::EqualsCaseInsensitiveASCII(split[1], "CONTINUE")) {
127     result.action = NsswitchReader::Action::kContinue;
128   } else if (base::EqualsCaseInsensitiveASCII(split[1], "MERGE")) {
129     result.action = NsswitchReader::Action::kMerge;
130   }
131 
132   return result;
133 }
134 
TokenizeActions(base::StringPiece actions)135 std::vector<NsswitchReader::ServiceAction> TokenizeActions(
136     base::StringPiece actions) {
137   DCHECK(!actions.empty());
138   DCHECK_NE(actions.front(), '[');
139   DCHECK_EQ(actions.find(']'), base::StringPiece::npos);
140   DCHECK(!base::IsAsciiWhitespace(actions.front()));
141 
142   std::vector<NsswitchReader::ServiceAction> result;
143 
144   for (const auto& action_column : base::SplitStringPiece(
145            actions, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
146            base::SPLIT_WANT_NONEMPTY)) {
147     DCHECK(!action_column.empty());
148     result.push_back(TokenizeAction(action_column));
149   }
150 
151   return result;
152 }
153 
TokenizeService(base::StringPiece service_column)154 NsswitchReader::ServiceSpecification TokenizeService(
155     base::StringPiece service_column) {
156   DCHECK(!service_column.empty());
157   DCHECK_EQ(service_column.find_first_of(base::kWhitespaceASCII),
158             base::StringPiece::npos);
159   DCHECK_NE(service_column.front(), '[');
160 
161   if (base::EqualsCaseInsensitiveASCII(service_column, "files")) {
162     return NsswitchReader::ServiceSpecification(
163         NsswitchReader::Service::kFiles);
164   }
165   if (base::EqualsCaseInsensitiveASCII(service_column, "dns")) {
166     return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns);
167   }
168   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns")) {
169     return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns);
170   }
171   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns4")) {
172     return NsswitchReader::ServiceSpecification(
173         NsswitchReader::Service::kMdns4);
174   }
175   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns6")) {
176     return NsswitchReader::ServiceSpecification(
177         NsswitchReader::Service::kMdns6);
178   }
179   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns_minimal")) {
180     return NsswitchReader::ServiceSpecification(
181         NsswitchReader::Service::kMdnsMinimal);
182   }
183   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns4_minimal")) {
184     return NsswitchReader::ServiceSpecification(
185         NsswitchReader::Service::kMdns4Minimal);
186   }
187   if (base::EqualsCaseInsensitiveASCII(service_column, "mdns6_minimal")) {
188     return NsswitchReader::ServiceSpecification(
189         NsswitchReader::Service::kMdns6Minimal);
190   }
191   if (base::EqualsCaseInsensitiveASCII(service_column, "myhostname")) {
192     return NsswitchReader::ServiceSpecification(
193         NsswitchReader::Service::kMyHostname);
194   }
195   if (base::EqualsCaseInsensitiveASCII(service_column, "resolve")) {
196     return NsswitchReader::ServiceSpecification(
197         NsswitchReader::Service::kResolve);
198   }
199   if (base::EqualsCaseInsensitiveASCII(service_column, "nis")) {
200     return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kNis);
201   }
202 
203   return NsswitchReader::ServiceSpecification(
204       NsswitchReader::Service::kUnknown);
205 }
206 
207 // Returns the actions string without brackets. `out_num_bytes` returns number
208 // of bytes in the actions including brackets and trailing whitespace.
GetActionsStringAndRemoveBrackets(base::StringPiece database,size_t & out_num_bytes)209 base::StringPiece GetActionsStringAndRemoveBrackets(base::StringPiece database,
210                                                     size_t& out_num_bytes) {
211   DCHECK(!database.empty());
212   DCHECK_EQ(database.front(), '[');
213 
214   size_t action_end = database.find(']');
215 
216   base::StringPiece actions;
217   if (action_end == base::StringPiece::npos) {
218     actions = database.substr(1);
219     out_num_bytes = database.size();
220   } else {
221     actions = database.substr(1, action_end - 1);
222     out_num_bytes = action_end;
223   }
224 
225   // Ignore repeated '[' at start of `actions`.
226   actions =
227       base::TrimWhitespaceASCII(actions, base::TrimPositions::TRIM_LEADING);
228   while (!actions.empty() && actions.front() == '[') {
229     actions = base::TrimWhitespaceASCII(actions.substr(1),
230                                         base::TrimPositions::TRIM_LEADING);
231   }
232 
233   // Include any trailing ']' and whitespace in `out_num_bytes`.
234   while (out_num_bytes < database.size() &&
235          (database[out_num_bytes] == ']' ||
236           base::IsAsciiWhitespace(database[out_num_bytes]))) {
237     ++out_num_bytes;
238   }
239 
240   return actions;
241 }
242 
TokenizeDatabase(base::StringPiece database)243 std::vector<NsswitchReader::ServiceSpecification> TokenizeDatabase(
244     base::StringPiece database) {
245   std::vector<NsswitchReader::ServiceSpecification> tokenized;
246 
247   while (!database.empty()) {
248     DCHECK(!base::IsAsciiWhitespace(database.front()));
249 
250     // Note: Assuming comments are not recognized mid-action or mid-service.
251     if (database.front() == '#') {
252       // Once a comment is hit, the rest of the database is comment.
253       return tokenized;
254     }
255 
256     if (database.front() == '[') {
257       // Actions are expected to come after a service.
258       if (tokenized.empty()) {
259         tokenized.emplace_back(NsswitchReader::Service::kUnknown);
260       }
261 
262       size_t num_actions_bytes = 0;
263       base::StringPiece actions =
264           GetActionsStringAndRemoveBrackets(database, num_actions_bytes);
265 
266       if (num_actions_bytes == database.size()) {
267         database = "";
268       } else {
269         database = database.substr(num_actions_bytes);
270       }
271 
272       if (!actions.empty()) {
273         std::vector<NsswitchReader::ServiceAction> tokenized_actions =
274             TokenizeActions(actions);
275         tokenized.back().actions.insert(tokenized.back().actions.end(),
276                                         tokenized_actions.begin(),
277                                         tokenized_actions.end());
278       }
279     } else {
280       size_t column_end = database.find_first_of(base::kWhitespaceASCII);
281 
282       base::StringPiece service_column;
283       if (column_end == base::StringPiece::npos) {
284         service_column = database;
285         database = "";
286       } else {
287         service_column = database.substr(0, column_end);
288         database = database.substr(column_end);
289       }
290 
291       tokenized.push_back(TokenizeService(service_column));
292     }
293 
294     database =
295         base::TrimWhitespaceASCII(database, base::TrimPositions::TRIM_LEADING);
296   }
297 
298   return tokenized;
299 }
300 
GetDefaultHosts()301 std::vector<NsswitchReader::ServiceSpecification> GetDefaultHosts() {
302   return {NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles),
303           NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns)};
304 }
305 
306 }  // namespace
307 
ServiceSpecification(Service service,std::vector<ServiceAction> actions)308 NsswitchReader::ServiceSpecification::ServiceSpecification(
309     Service service,
310     std::vector<ServiceAction> actions)
311     : service(service), actions(std::move(actions)) {}
312 
313 NsswitchReader::ServiceSpecification::~ServiceSpecification() = default;
314 
315 NsswitchReader::ServiceSpecification::ServiceSpecification(
316     const ServiceSpecification&) = default;
317 
318 NsswitchReader::ServiceSpecification&
319 NsswitchReader::ServiceSpecification::operator=(const ServiceSpecification&) =
320     default;
321 
322 NsswitchReader::ServiceSpecification::ServiceSpecification(
323     ServiceSpecification&&) = default;
324 
325 NsswitchReader::ServiceSpecification&
326 NsswitchReader::ServiceSpecification::operator=(ServiceSpecification&&) =
327     default;
328 
NsswitchReader()329 NsswitchReader::NsswitchReader()
330     : file_read_call_(base::BindRepeating(&ReadNsswitch)) {}
331 
332 NsswitchReader::~NsswitchReader() = default;
333 
334 std::vector<NsswitchReader::ServiceSpecification>
ReadAndParseHosts()335 NsswitchReader::ReadAndParseHosts() {
336   std::string file = file_read_call_.Run();
337   if (file.empty())
338     return GetDefaultHosts();
339 
340   base::StringPiece hosts = FindDatabase(file, "hosts:");
341   UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.HostsFound",
342                         !hosts.empty());
343   if (hosts.empty())
344     return GetDefaultHosts();
345 
346   return TokenizeDatabase(hosts);
347 }
348 
349 }  // namespace net
350