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 <string_view>
9 #include <utility>
10 #include <vector>
11
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/functional/bind.h"
15 #include "base/functional/callback.h"
16 #include "base/metrics/histogram_macros.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(std::string_view text)57 std::string_view SkipRestOfLine(std::string_view text) {
58 std::string_view::size_type line_end = text.find('\n');
59 if (line_end == std::string_view::npos) {
60 return "";
61 }
62 return text.substr(line_end);
63 }
64
65 // In case of multiple entries for `database_name`, finds only the first.
FindDatabase(std::string_view text,std::string_view database_name)66 std::string_view FindDatabase(std::string_view text,
67 std::string_view database_name) {
68 DCHECK(!text.empty());
69 DCHECK(!database_name.empty());
70 DCHECK(!database_name.starts_with("#"));
71 DCHECK(!base::IsAsciiWhitespace(database_name.front()));
72 DCHECK(database_name.ends_with(":"));
73
74 while (!text.empty()) {
75 text = base::TrimWhitespaceASCII(text, base::TrimPositions::TRIM_LEADING);
76
77 if (base::StartsWith(text, database_name,
78 base::CompareCase::INSENSITIVE_ASCII)) {
79 DCHECK(!text.starts_with("#"));
80
81 text = text.substr(database_name.size());
82 std::string_view::size_type line_end = text.find('\n');
83 if (line_end != std::string_view::npos) {
84 text = text.substr(0, line_end);
85 }
86
87 return base::TrimWhitespaceASCII(text, base::TrimPositions::TRIM_ALL);
88 }
89
90 text = SkipRestOfLine(text);
91 }
92
93 return "";
94 }
95
TokenizeAction(std::string_view action_column)96 NsswitchReader::ServiceAction TokenizeAction(std::string_view action_column) {
97 DCHECK(!action_column.empty());
98 DCHECK_EQ(action_column.find(']'), std::string_view::npos);
99 DCHECK_EQ(action_column.find_first_of(base::kWhitespaceASCII),
100 std::string_view::npos);
101
102 NsswitchReader::ServiceAction result = {/*negated=*/false,
103 NsswitchReader::Status::kUnknown,
104 NsswitchReader::Action::kUnknown};
105
106 std::vector<std::string_view> split = base::SplitStringPiece(
107 action_column, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
108 if (split.size() != 2)
109 return result;
110
111 if (split[0].size() >= 2 && split[0].front() == '!') {
112 result.negated = true;
113 split[0] = split[0].substr(1);
114 }
115
116 if (base::EqualsCaseInsensitiveASCII(split[0], "SUCCESS")) {
117 result.status = NsswitchReader::Status::kSuccess;
118 } else if (base::EqualsCaseInsensitiveASCII(split[0], "NOTFOUND")) {
119 result.status = NsswitchReader::Status::kNotFound;
120 } else if (base::EqualsCaseInsensitiveASCII(split[0], "UNAVAIL")) {
121 result.status = NsswitchReader::Status::kUnavailable;
122 } else if (base::EqualsCaseInsensitiveASCII(split[0], "TRYAGAIN")) {
123 result.status = NsswitchReader::Status::kTryAgain;
124 }
125
126 if (base::EqualsCaseInsensitiveASCII(split[1], "RETURN")) {
127 result.action = NsswitchReader::Action::kReturn;
128 } else if (base::EqualsCaseInsensitiveASCII(split[1], "CONTINUE")) {
129 result.action = NsswitchReader::Action::kContinue;
130 } else if (base::EqualsCaseInsensitiveASCII(split[1], "MERGE")) {
131 result.action = NsswitchReader::Action::kMerge;
132 }
133
134 return result;
135 }
136
TokenizeActions(std::string_view actions)137 std::vector<NsswitchReader::ServiceAction> TokenizeActions(
138 std::string_view actions) {
139 DCHECK(!actions.empty());
140 DCHECK_NE(actions.front(), '[');
141 DCHECK_EQ(actions.find(']'), std::string_view::npos);
142 DCHECK(!base::IsAsciiWhitespace(actions.front()));
143
144 std::vector<NsswitchReader::ServiceAction> result;
145
146 for (const auto& action_column : base::SplitStringPiece(
147 actions, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
148 base::SPLIT_WANT_NONEMPTY)) {
149 DCHECK(!action_column.empty());
150 result.push_back(TokenizeAction(action_column));
151 }
152
153 return result;
154 }
155
TokenizeService(std::string_view service_column)156 NsswitchReader::ServiceSpecification TokenizeService(
157 std::string_view service_column) {
158 DCHECK(!service_column.empty());
159 DCHECK_EQ(service_column.find_first_of(base::kWhitespaceASCII),
160 std::string_view::npos);
161 DCHECK_NE(service_column.front(), '[');
162
163 if (base::EqualsCaseInsensitiveASCII(service_column, "files")) {
164 return NsswitchReader::ServiceSpecification(
165 NsswitchReader::Service::kFiles);
166 }
167 if (base::EqualsCaseInsensitiveASCII(service_column, "dns")) {
168 return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns);
169 }
170 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns")) {
171 return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kMdns);
172 }
173 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns4")) {
174 return NsswitchReader::ServiceSpecification(
175 NsswitchReader::Service::kMdns4);
176 }
177 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns6")) {
178 return NsswitchReader::ServiceSpecification(
179 NsswitchReader::Service::kMdns6);
180 }
181 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns_minimal")) {
182 return NsswitchReader::ServiceSpecification(
183 NsswitchReader::Service::kMdnsMinimal);
184 }
185 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns4_minimal")) {
186 return NsswitchReader::ServiceSpecification(
187 NsswitchReader::Service::kMdns4Minimal);
188 }
189 if (base::EqualsCaseInsensitiveASCII(service_column, "mdns6_minimal")) {
190 return NsswitchReader::ServiceSpecification(
191 NsswitchReader::Service::kMdns6Minimal);
192 }
193 if (base::EqualsCaseInsensitiveASCII(service_column, "myhostname")) {
194 return NsswitchReader::ServiceSpecification(
195 NsswitchReader::Service::kMyHostname);
196 }
197 if (base::EqualsCaseInsensitiveASCII(service_column, "resolve")) {
198 return NsswitchReader::ServiceSpecification(
199 NsswitchReader::Service::kResolve);
200 }
201 if (base::EqualsCaseInsensitiveASCII(service_column, "nis")) {
202 return NsswitchReader::ServiceSpecification(NsswitchReader::Service::kNis);
203 }
204
205 return NsswitchReader::ServiceSpecification(
206 NsswitchReader::Service::kUnknown);
207 }
208
209 // Returns the actions string without brackets. `out_num_bytes` returns number
210 // of bytes in the actions including brackets and trailing whitespace.
GetActionsStringAndRemoveBrackets(std::string_view database,size_t & out_num_bytes)211 std::string_view GetActionsStringAndRemoveBrackets(std::string_view database,
212 size_t& out_num_bytes) {
213 DCHECK(!database.empty());
214 DCHECK_EQ(database.front(), '[');
215
216 size_t action_end = database.find(']');
217
218 std::string_view actions;
219 if (action_end == std::string_view::npos) {
220 actions = database.substr(1);
221 out_num_bytes = database.size();
222 } else {
223 actions = database.substr(1, action_end - 1);
224 out_num_bytes = action_end;
225 }
226
227 // Ignore repeated '[' at start of `actions`.
228 actions =
229 base::TrimWhitespaceASCII(actions, base::TrimPositions::TRIM_LEADING);
230 while (!actions.empty() && actions.front() == '[') {
231 actions = base::TrimWhitespaceASCII(actions.substr(1),
232 base::TrimPositions::TRIM_LEADING);
233 }
234
235 // Include any trailing ']' and whitespace in `out_num_bytes`.
236 while (out_num_bytes < database.size() &&
237 (database[out_num_bytes] == ']' ||
238 base::IsAsciiWhitespace(database[out_num_bytes]))) {
239 ++out_num_bytes;
240 }
241
242 return actions;
243 }
244
TokenizeDatabase(std::string_view database)245 std::vector<NsswitchReader::ServiceSpecification> TokenizeDatabase(
246 std::string_view database) {
247 std::vector<NsswitchReader::ServiceSpecification> tokenized;
248
249 while (!database.empty()) {
250 DCHECK(!base::IsAsciiWhitespace(database.front()));
251
252 // Note: Assuming comments are not recognized mid-action or mid-service.
253 if (database.front() == '#') {
254 // Once a comment is hit, the rest of the database is comment.
255 return tokenized;
256 }
257
258 if (database.front() == '[') {
259 // Actions are expected to come after a service.
260 if (tokenized.empty()) {
261 tokenized.emplace_back(NsswitchReader::Service::kUnknown);
262 }
263
264 size_t num_actions_bytes = 0;
265 std::string_view actions =
266 GetActionsStringAndRemoveBrackets(database, num_actions_bytes);
267
268 if (num_actions_bytes == database.size()) {
269 database = "";
270 } else {
271 database = database.substr(num_actions_bytes);
272 }
273
274 if (!actions.empty()) {
275 std::vector<NsswitchReader::ServiceAction> tokenized_actions =
276 TokenizeActions(actions);
277 tokenized.back().actions.insert(tokenized.back().actions.end(),
278 tokenized_actions.begin(),
279 tokenized_actions.end());
280 }
281 } else {
282 size_t column_end = database.find_first_of(base::kWhitespaceASCII);
283
284 std::string_view service_column;
285 if (column_end == std::string_view::npos) {
286 service_column = database;
287 database = "";
288 } else {
289 service_column = database.substr(0, column_end);
290 database = database.substr(column_end);
291 }
292
293 tokenized.push_back(TokenizeService(service_column));
294 }
295
296 database =
297 base::TrimWhitespaceASCII(database, base::TrimPositions::TRIM_LEADING);
298 }
299
300 return tokenized;
301 }
302
GetDefaultHosts()303 std::vector<NsswitchReader::ServiceSpecification> GetDefaultHosts() {
304 return {NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles),
305 NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns)};
306 }
307
308 } // namespace
309
ServiceSpecification(Service service,std::vector<ServiceAction> actions)310 NsswitchReader::ServiceSpecification::ServiceSpecification(
311 Service service,
312 std::vector<ServiceAction> actions)
313 : service(service), actions(std::move(actions)) {}
314
315 NsswitchReader::ServiceSpecification::~ServiceSpecification() = default;
316
317 NsswitchReader::ServiceSpecification::ServiceSpecification(
318 const ServiceSpecification&) = default;
319
320 NsswitchReader::ServiceSpecification&
321 NsswitchReader::ServiceSpecification::operator=(const ServiceSpecification&) =
322 default;
323
324 NsswitchReader::ServiceSpecification::ServiceSpecification(
325 ServiceSpecification&&) = default;
326
327 NsswitchReader::ServiceSpecification&
328 NsswitchReader::ServiceSpecification::operator=(ServiceSpecification&&) =
329 default;
330
NsswitchReader()331 NsswitchReader::NsswitchReader()
332 : file_read_call_(base::BindRepeating(&ReadNsswitch)) {}
333
334 NsswitchReader::~NsswitchReader() = default;
335
336 std::vector<NsswitchReader::ServiceSpecification>
ReadAndParseHosts()337 NsswitchReader::ReadAndParseHosts() {
338 std::string file = file_read_call_.Run();
339 if (file.empty())
340 return GetDefaultHosts();
341
342 std::string_view hosts = FindDatabase(file, "hosts:");
343 if (hosts.empty())
344 return GetDefaultHosts();
345
346 return TokenizeDatabase(hosts);
347 }
348
349 } // namespace net
350