xref: /aosp_15_r20/external/cronet/net/dns/nsswitch_reader.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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