blob: dfe067992f7b2b78d0e495251f50b11743f1b908 [file] [log] [blame]
Avi Drissman64595482022-09-14 20:52:291// Copyright 2021 The Chromium Authors
Eric Orth54ce38f2021-04-06 18:19:482// 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
Eric Orth54ce38f2021-04-06 18:19:4811#include "base/files/file_path.h"
12#include "base/files/file_util.h"
Avi Drissman41c4a412023-01-11 22:45:3713#include "base/functional/bind.h"
14#include "base/functional/callback.h"
Eric Ortha4b7ca02021-04-09 22:28:5815#include "base/metrics/histogram_macros.h"
Eric Orth54ce38f2021-04-06 18:19:4816#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
Xiaohan Wang2a6845b2022-01-08 04:40:5721#if BUILDFLAG(IS_POSIX)
Eric Orth54ce38f2021-04-06 18:19:4822#include <netdb.h>
23#endif // defined (OS_POSIX)
24
25namespace net {
26
27namespace {
28
29#ifdef _PATH_NSSWITCH_CONF
30constexpr base::FilePath::CharType kNsswitchPath[] =
31 FILE_PATH_LITERAL(_PATH_NSSWITCH_CONF);
32#else
33constexpr 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).
40constexpr size_t kMaxFileSize = 1024 * 1024;
41
42std::string ReadNsswitch() {
43 std::string file;
Eric Ortha4b7ca02021-04-09 22:28:5844 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);
Eric Orth54ce38f2021-04-06 18:19:4850
Eric Ortha4b7ca02021-04-09 22:28:5851 if (result)
52 return file;
53
54 return "";
Eric Orth54ce38f2021-04-06 18:19:4855}
56
57base::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.
65base::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
Eric Orth35c0c242021-04-06 22:58:1394NsswitchReader::ServiceAction TokenizeAction(base::StringPiece action_column) {
95 DCHECK(!action_column.empty());
Eric Orth72dd9c3f2021-04-13 22:48:0496 DCHECK_EQ(action_column.find(']'), base::StringPiece::npos);
Eric Orth35c0c242021-04-06 22:58:1397 DCHECK_EQ(action_column.find_first_of(base::kWhitespaceASCII),
98 base::StringPiece::npos);
Eric Orth35c0c242021-04-06 22:58:1399
Eric Orth54ce38f2021-04-06 18:19:48100 NsswitchReader::ServiceAction result = {/*negated=*/false,
101 NsswitchReader::Status::kUnknown,
102 NsswitchReader::Action::kUnknown};
103
Eric Orth54ce38f2021-04-06 18:19:48104 std::vector<base::StringPiece> split = base::SplitStringPiece(
Eric Orth35c0c242021-04-06 22:58:13105 action_column, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
Eric Orth54ce38f2021-04-06 18:19:48106 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
Eric Orth35c0c242021-04-06 22:58:13135std::vector<NsswitchReader::ServiceAction> TokenizeActions(
136 base::StringPiece actions) {
Eric Orth35c0c242021-04-06 22:58:13137 DCHECK(!actions.empty());
Eric Orth72dd9c3f2021-04-13 22:48:04138 DCHECK_NE(actions.front(), '[');
139 DCHECK_EQ(actions.find(']'), base::StringPiece::npos);
Eric Orth35c0c242021-04-06 22:58:13140 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
154NsswitchReader::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);
Eric Orth72dd9c3f2021-04-13 22:48:04159 DCHECK_NE(service_column.front(), '[');
Eric Orth35c0c242021-04-06 22:58:13160
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
Eric Orth72dd9c3f2021-04-13 22:48:04207// Returns the actions string without brackets. `out_num_bytes` returns number
208// of bytes in the actions including brackets and trailing whitespace.
209base::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
Eric Orth54ce38f2021-04-06 18:19:48243std::vector<NsswitchReader::ServiceSpecification> TokenizeDatabase(
244 base::StringPiece database) {
245 std::vector<NsswitchReader::ServiceSpecification> tokenized;
246
Eric Orth35c0c242021-04-06 22:58:13247 while (!database.empty()) {
248 DCHECK(!base::IsAsciiWhitespace(database.front()));
Eric Orth54ce38f2021-04-06 18:19:48249
Eric Orth35c0c242021-04-06 22:58:13250 // Note: Assuming comments are not recognized mid-action or mid-service.
251 if (database.front() == '#') {
Eric Orth54ce38f2021-04-06 18:19:48252 // Once a comment is hit, the rest of the database is comment.
253 return tokenized;
254 }
255
Eric Orth35c0c242021-04-06 22:58:13256 if (database.front() == '[') {
Eric Orth54ce38f2021-04-06 18:19:48257 // Actions are expected to come after a service.
258 if (tokenized.empty()) {
259 tokenized.emplace_back(NsswitchReader::Service::kUnknown);
260 }
261
Eric Orth72dd9c3f2021-04-13 22:48:04262 size_t num_actions_bytes = 0;
263 base::StringPiece actions =
264 GetActionsStringAndRemoveBrackets(database, num_actions_bytes);
Eric Orth35c0c242021-04-06 22:58:13265
Eric Orth72dd9c3f2021-04-13 22:48:04266 if (num_actions_bytes == database.size()) {
Eric Orth35c0c242021-04-06 22:58:13267 database = "";
268 } else {
Eric Orth72dd9c3f2021-04-13 22:48:04269 database = database.substr(num_actions_bytes);
Eric Orth35c0c242021-04-06 22:58:13270 }
Eric Orth35c0c242021-04-06 22:58:13271
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 }
Eric Orth54ce38f2021-04-06 18:19:48279 } else {
Eric Orth35c0c242021-04-06 22:58:13280 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));
Eric Orth54ce38f2021-04-06 18:19:48292 }
Eric Orth35c0c242021-04-06 22:58:13293
294 database =
295 base::TrimWhitespaceASCII(database, base::TrimPositions::TRIM_LEADING);
Eric Orth54ce38f2021-04-06 18:19:48296 }
297
298 return tokenized;
299}
300
301std::vector<NsswitchReader::ServiceSpecification> GetDefaultHosts() {
302 return {NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles),
303 NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns)};
304}
305
306} // namespace
307
308NsswitchReader::ServiceSpecification::ServiceSpecification(
309 Service service,
310 std::vector<ServiceAction> actions)
311 : service(service), actions(std::move(actions)) {}
312
313NsswitchReader::ServiceSpecification::~ServiceSpecification() = default;
314
315NsswitchReader::ServiceSpecification::ServiceSpecification(
316 const ServiceSpecification&) = default;
317
318NsswitchReader::ServiceSpecification&
319NsswitchReader::ServiceSpecification::operator=(const ServiceSpecification&) =
320 default;
321
322NsswitchReader::ServiceSpecification::ServiceSpecification(
323 ServiceSpecification&&) = default;
324
325NsswitchReader::ServiceSpecification&
326NsswitchReader::ServiceSpecification::operator=(ServiceSpecification&&) =
327 default;
328
329NsswitchReader::NsswitchReader()
330 : file_read_call_(base::BindRepeating(&ReadNsswitch)) {}
331
332NsswitchReader::~NsswitchReader() = default;
333
334std::vector<NsswitchReader::ServiceSpecification>
335NsswitchReader::ReadAndParseHosts() {
336 std::string file = file_read_call_.Run();
337 if (file.empty())
338 return GetDefaultHosts();
339
340 base::StringPiece hosts = FindDatabase(file, "hosts:");
Eric Ortha4b7ca02021-04-09 22:28:58341 UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.HostsFound",
342 !hosts.empty());
Eric Orth54ce38f2021-04-06 18:19:48343 if (hosts.empty())
344 return GetDefaultHosts();
345
346 return TokenizeDatabase(hosts);
347}
348
349} // namespace net