1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
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 <stddef.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 
9 #include <locale>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14 
15 #include "base/at_exit.h"
16 #include "base/bind.h"
17 #include "base/callback.h"
18 #include "base/command_line.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.h"
21 #include "base/json/json_reader.h"
22 #include "base/lazy_instance.h"
23 #include "base/logging.h"
24 #include "base/message_loop/message_pump_type.h"
25 #include "base/run_loop.h"
26 #include "base/single_thread_task_runner.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string_number_conversions.h"
29 #include "base/strings/string_split.h"
30 #include "base/strings/string_util.h"
31 #include "base/strings/stringprintf.h"
32 #include "base/synchronization/waitable_event.h"
33 #include "base/task/single_thread_task_executor.h"
34 #include "base/task/thread_pool/thread_pool_instance.h"
35 #include "base/threading/thread.h"
36 #include "base/threading/thread_local.h"
37 #include "base/threading/thread_task_runner_handle.h"
38 #include "build/build_config.h"
39 #include "chrome/test/chromedriver/constants/version.h"
40 #include "chrome/test/chromedriver/logging.h"
41 #include "chrome/test/chromedriver/server/http_handler.h"
42 #include "chrome/test/chromedriver/server/http_server.h"
43 #include "mojo/core/embedder/embedder.h"
44 #include "net/base/ip_address.h"
45 #include "net/base/ip_endpoint.h"
46 #include "net/base/net_errors.h"
47 #include "net/log/net_log_source.h"
48 
49 namespace {
50 
51 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
52 // Ensure that there is a writable shared memory directory. We use
53 // network::SimpleURLLoader to connect to Chrome, and it calls
54 // base::subtle::PlatformSharedMemoryRegion::Create to get a shared memory
55 // region. network::SimpleURLLoader would fail if the shared memory directory is
56 // not accessible. We work around this issue by adding --disable-dev-shm-usage
57 // to command line, to use an alternative directory for shared memory.
58 // See https://crbug.com/chromedriver/2782.
EnsureSharedMemory(base::CommandLine * cmd_line)59 void EnsureSharedMemory(base::CommandLine* cmd_line) {
60   if (!cmd_line->HasSwitch("disable-dev-shm-usage")) {
61     base::FilePath directory;
62     if (GetShmemTempDir(false, &directory) &&
63         access(directory.value().c_str(), W_OK | X_OK) < 0) {
64       VLOG(0) << directory
65               << " not writable, adding --disable-dev-shm-usage switch";
66       cmd_line->AppendSwitch("disable-dev-shm-usage");
67     }
68   }
69 }
70 #endif
71 
SendResponseOnCmdThread(const scoped_refptr<base::SingleThreadTaskRunner> & io_task_runner,const HttpResponseSenderFunc & send_response_on_io_func,std::unique_ptr<net::HttpServerResponseInfo> response)72 void SendResponseOnCmdThread(
73     const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
74     const HttpResponseSenderFunc& send_response_on_io_func,
75     std::unique_ptr<net::HttpServerResponseInfo> response) {
76   io_task_runner->PostTask(
77       FROM_HERE, base::BindOnce(send_response_on_io_func, std::move(response)));
78 }
79 
HandleRequestOnCmdThread(HttpHandler * handler,const std::vector<net::IPAddress> & allowed_ips,const net::HttpServerRequestInfo & request,const HttpResponseSenderFunc & send_response_func)80 void HandleRequestOnCmdThread(
81     HttpHandler* handler,
82     const std::vector<net::IPAddress>& allowed_ips,
83     const net::HttpServerRequestInfo& request,
84     const HttpResponseSenderFunc& send_response_func) {
85   if (!allowed_ips.empty()) {
86     const net::IPAddress& peer_address = request.peer.address();
87     if (!base::Contains(allowed_ips, peer_address)) {
88       LOG(WARNING) << "unauthorized access from " << request.peer.ToString();
89       std::unique_ptr<net::HttpServerResponseInfo> response(
90           new net::HttpServerResponseInfo(net::HTTP_UNAUTHORIZED));
91       response->SetBody("Unauthorized access", "text/plain");
92       send_response_func.Run(std::move(response));
93       return;
94     }
95   }
96 
97   handler->Handle(request, send_response_func);
98 }
99 
HandleRequestOnIOThread(const scoped_refptr<base::SingleThreadTaskRunner> & cmd_task_runner,const HttpRequestHandlerFunc & handle_request_on_cmd_func,const net::HttpServerRequestInfo & request,const HttpResponseSenderFunc & send_response_func)100 void HandleRequestOnIOThread(
101     const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner,
102     const HttpRequestHandlerFunc& handle_request_on_cmd_func,
103     const net::HttpServerRequestInfo& request,
104     const HttpResponseSenderFunc& send_response_func) {
105   cmd_task_runner->PostTask(
106       FROM_HERE,
107       base::BindOnce(handle_request_on_cmd_func, request,
108                      base::BindRepeating(&SendResponseOnCmdThread,
109                                          base::ThreadTaskRunnerHandle::Get(),
110                                          send_response_func)));
111 }
112 
113 base::LazyInstance<base::ThreadLocalPointer<HttpServer>>::DestructorAtExit
114     lazy_tls_server_ipv4 = LAZY_INSTANCE_INITIALIZER;
115 base::LazyInstance<base::ThreadLocalPointer<HttpServer>>::DestructorAtExit
116     lazy_tls_server_ipv6 = LAZY_INSTANCE_INITIALIZER;
117 
StopServerOnIOThread()118 void StopServerOnIOThread() {
119   // Note, |server| may be NULL.
120   HttpServer* server = lazy_tls_server_ipv4.Pointer()->Get();
121   lazy_tls_server_ipv4.Pointer()->Set(NULL);
122   delete server;
123 
124   server = lazy_tls_server_ipv6.Pointer()->Get();
125   lazy_tls_server_ipv6.Pointer()->Set(NULL);
126   delete server;
127 }
128 
StartServerOnIOThread(uint16_t port,bool allow_remote,const std::string & url_base,const std::vector<net::IPAddress> & allowed_ips,const HttpRequestHandlerFunc & handle_request_func,base::WeakPtr<HttpHandler> handler,const scoped_refptr<base::SingleThreadTaskRunner> & cmd_task_runner)129 void StartServerOnIOThread(
130     uint16_t port,
131     bool allow_remote,
132     const std::string& url_base,
133     const std::vector<net::IPAddress>& allowed_ips,
134     const HttpRequestHandlerFunc& handle_request_func,
135     base::WeakPtr<HttpHandler> handler,
136     const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner) {
137   std::unique_ptr<HttpServer> temp_server;
138 
139 // On Linux and Windows, we listen to IPv6 first, and then optionally listen
140 // to IPv4 (depending on |need_ipv4| below). The reason is listening to an
141 // IPv6 port may automatically listen to the same IPv4 port as well, and would
142 // return an error if the IPv4 port is already in use.
143 //
144 // On Mac, however, we listen to IPv4 first before listening to IPv6. If we
145 // were to listen to IPv6 first, it would succeed whether the corresponding
146 // IPv4 port is in use or not, and we wouldn't know if we ended up listening
147 // to both IPv4 and IPv6 ports, or only IPv6 port. Listening to IPv4 first
148 // ensures that we successfully listen to both IPv4 and IPv6.
149 
150 #if defined(OS_MAC)
151   temp_server = std::make_unique<HttpServer>(
152       url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
153   int ipv4_status = temp_server->Start(port, allow_remote, true);
154   if (ipv4_status == net::OK) {
155     lazy_tls_server_ipv4.Pointer()->Set(temp_server.release());
156   } else if (ipv4_status == net::ERR_ADDRESS_IN_USE) {
157     // ERR_ADDRESS_IN_USE causes an immediate exit, since it indicates the port
158     // is being used by another process. Other errors are assumed to indicate
159     // that IPv4 isn't available for some reason, e.g., on an IPv6-only host.
160     // Thus the error doesn't cause an exit immediately. The HttpServer::Start
161     // method has already printed a message indicating what has happened. Later,
162     // near the end of this function, we exit if both IPv4 and IPv6 failed.
163     printf("IPv4 port not available. Exiting...\n");
164     exit(1);
165   }
166 #endif
167 
168   temp_server = std::make_unique<HttpServer>(
169       url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
170   int ipv6_status = temp_server->Start(port, allow_remote, false);
171   if (ipv6_status == net::OK) {
172     lazy_tls_server_ipv6.Pointer()->Set(temp_server.release());
173   } else if (ipv6_status == net::ERR_ADDRESS_IN_USE) {
174     printf("IPv6 port not available. Exiting...\n");
175     exit(1);
176   }
177 
178 #if !defined(OS_MAC)
179   // In some cases, binding to an IPv6 port also binds to the same IPv4 port.
180   // The following code determines if it is necessary to bind to IPv4 port.
181   enum class NeedIPv4 { NOT_NEEDED, UNKNOWN, NEEDED } need_ipv4;
182   // Dual-protocol bind deosn't work while binding to localhost (!allow_remote).
183   if (!allow_remote || ipv6_status != net::OK) {
184     need_ipv4 = NeedIPv4::NEEDED;
185   } else {
186 // Currently, the network layer provides no way for us to control dual-protocol
187 // bind option, or to query the current setting of that option, so we do our
188 // best to determine the current setting. See https://crbug.com/858892.
189 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
190     // On Linux, dual-protocol bind is controlled by a system file.
191     // ChromeOS builds also have OS_LINUX defined, so the code below applies.
192     std::string bindv6only;
193     base::FilePath bindv6only_filename("/proc/sys/net/ipv6/bindv6only");
194     if (!base::ReadFileToString(bindv6only_filename, &bindv6only)) {
195       LOG(WARNING) << "Unable to read " << bindv6only_filename << ".";
196       need_ipv4 = NeedIPv4::UNKNOWN;
197     } else if (bindv6only == "1\n") {
198       need_ipv4 = NeedIPv4::NEEDED;
199     } else if (bindv6only == "0\n") {
200       need_ipv4 = NeedIPv4::NOT_NEEDED;
201     } else {
202       LOG(WARNING) << "Unexpected " << bindv6only_filename << " contents.";
203       need_ipv4 = NeedIPv4::UNKNOWN;
204     }
205 #elif defined(OS_WIN)
206     // On Windows, the net component always enables dual-protocol bind. See
207     // https://chromium.googlesource.com/chromium/src/+/69.0.3464.0/net/socket/socket_descriptor.cc#28.
208     need_ipv4 = NeedIPv4::NOT_NEEDED;
209 #else
210     LOG(WARNING) << "Running on a platform not officially supported by "
211                  << kChromeDriverProductFullName << ".";
212     need_ipv4 = NeedIPv4::UNKNOWN;
213 #endif
214   }
215   int ipv4_status;
216   if (need_ipv4 == NeedIPv4::NOT_NEEDED) {
217     ipv4_status = ipv6_status;
218   } else {
219     temp_server = std::make_unique<HttpServer>(
220         url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
221     ipv4_status = temp_server->Start(port, allow_remote, true);
222     if (ipv4_status == net::OK) {
223       lazy_tls_server_ipv4.Pointer()->Set(temp_server.release());
224     } else if (ipv4_status == net::ERR_ADDRESS_IN_USE) {
225       if (need_ipv4 == NeedIPv4::NEEDED) {
226         printf("IPv4 port not available. Exiting...\n");
227         exit(1);
228       } else {
229         printf("Unable to determine if bind to IPv4 port was successful.\n");
230       }
231     }
232   }
233 #endif  // !defined(OS_MAC)
234 
235   if (ipv4_status != net::OK && ipv6_status != net::OK) {
236     printf("Unable to start server with either IPv4 or IPv6. Exiting...\n");
237     exit(1);
238   }
239   printf("%s was started successfully.\n", kChromeDriverProductShortName);
240   fflush(stdout);
241 }
242 
RunServer(uint16_t port,bool allow_remote,const std::vector<net::IPAddress> & allowed_ips,const std::string & url_base,int adb_port)243 void RunServer(uint16_t port,
244                bool allow_remote,
245                const std::vector<net::IPAddress>& allowed_ips,
246                const std::string& url_base,
247                int adb_port) {
248   base::Thread io_thread(
249       base::StringPrintf("%s IO", kChromeDriverProductShortName));
250   CHECK(io_thread.StartWithOptions(
251       base::Thread::Options(base::MessagePumpType::IO, 0)));
252 
253   base::SingleThreadTaskExecutor main_task_executor;
254   base::RunLoop cmd_run_loop;
255   HttpHandler handler(cmd_run_loop.QuitClosure(), io_thread.task_runner(),
256                       main_task_executor.task_runner(), url_base, adb_port);
257   HttpRequestHandlerFunc handle_request_func =
258       base::BindRepeating(&HandleRequestOnCmdThread, &handler, allowed_ips);
259 
260   io_thread.task_runner()->PostTask(
261       FROM_HERE,
262       base::BindOnce(&StartServerOnIOThread, port, allow_remote, url_base,
263                      allowed_ips,
264                      base::BindRepeating(&HandleRequestOnIOThread,
265                                          main_task_executor.task_runner(),
266                                          handle_request_func),
267                      handler.WeakPtr(), main_task_executor.task_runner()));
268   // Run the command loop. This loop is quit after the response for a shutdown
269   // request is posted to the IO loop. After the command loop quits, a task
270   // is posted to the IO loop to stop the server. Lastly, the IO thread is
271   // destroyed, which waits until all pending tasks have been completed.
272   // This assumes the response is sent synchronously as part of the IO task.
273   cmd_run_loop.Run();
274   io_thread.task_runner()->PostTask(FROM_HERE,
275                                     base::BindOnce(&StopServerOnIOThread));
276 }
277 
278 }  // namespace
279 
main(int argc,char * argv[])280 int main(int argc, char *argv[]) {
281   base::CommandLine::Init(argc, argv);
282 
283   base::AtExitManager at_exit;
284   base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
285 
286 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_BSD)
287   // Select the locale from the environment by passing an empty string instead
288   // of the default "C" locale. This is particularly needed for the keycode
289   // conversion code to work.
290   setlocale(LC_ALL, "");
291 #endif
292 
293   // Parse command line flags.
294   uint16_t port = 9515;
295   int adb_port = 5037;
296   bool allow_remote = false;
297   std::vector<net::IPAddress> allowed_ips;
298   std::string allowlist;
299   std::string url_base;
300   if (cmd_line->HasSwitch("h") || cmd_line->HasSwitch("help")) {
301     std::string options;
302     const char* const kOptionAndDescriptions[] = {
303         "port=PORT",
304             "port to listen on",
305         "adb-port=PORT",
306             "adb server port",
307         "log-path=FILE",
308             "write server log to file instead of stderr, "
309             "increases log level to INFO",
310         "log-level=LEVEL",
311             "set log level: ALL, DEBUG, INFO, WARNING, SEVERE, OFF",
312         "verbose",
313             "log verbosely (equivalent to --log-level=ALL)",
314         "silent",
315             "log nothing (equivalent to --log-level=OFF)",
316         "append-log",
317             "append log file instead of rewriting",
318         "replayable",
319             "(experimental) log verbosely and don't truncate long "
320             "strings so that the log can be replayed.",
321         "version",
322             "print the version number and exit",
323         "url-base",
324             "base URL path prefix for commands, e.g. wd/url",
325         "readable-timestamp",
326             "add readable timestamps to log",
327         "enable-chrome-logs",
328             "show logs from the browser (overrides other logging options)",
329 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
330         "disable-dev-shm-usage",
331             "do not use /dev/shm "
332             "(add this switch if seeing errors related to shared memory)",
333 #endif
334     };
335     for (size_t i = 0; i < base::size(kOptionAndDescriptions) - 1; i += 2) {
336       options += base::StringPrintf(
337           "  --%-30s%s\n",
338           kOptionAndDescriptions[i], kOptionAndDescriptions[i + 1]);
339     }
340 
341     // Add helper info for allowed-ips since the product name may be
342     // different.
343     options += base::StringPrintf(
344         "  --%-30scomma-separated allowlist of remote IP addresses which are "
345         "allowed to connect to %s\n",
346         "allowed-ips", kChromeDriverProductShortName);
347 
348     printf("Usage: %s [OPTIONS]\n\nOptions\n%s", argv[0], options.c_str());
349     return 0;
350   }
351   bool early_exit = false;
352   if (cmd_line->HasSwitch("v") || cmd_line->HasSwitch("version")) {
353     printf("%s %s\n", kChromeDriverProductFullName, kChromeDriverVersion);
354     early_exit = true;
355   }
356   if (early_exit)
357     return 0;
358   if (cmd_line->HasSwitch("port")) {
359     int cmd_line_port;
360     if (!base::StringToInt(cmd_line->GetSwitchValueASCII("port"),
361                            &cmd_line_port) ||
362         cmd_line_port < 0 || cmd_line_port > 65535) {
363       printf("Invalid port. Exiting...\n");
364       return 1;
365     }
366     port = static_cast<uint16_t>(cmd_line_port);
367   }
368   if (cmd_line->HasSwitch("adb-port")) {
369     if (!base::StringToInt(cmd_line->GetSwitchValueASCII("adb-port"),
370                            &adb_port)) {
371       printf("Invalid adb-port. Exiting...\n");
372       return 1;
373     }
374   }
375   if (cmd_line->HasSwitch("url-base"))
376     url_base = cmd_line->GetSwitchValueASCII("url-base");
377   if (url_base.empty() || url_base.front() != '/')
378     url_base = "/" + url_base;
379   if (url_base.back() != '/')
380     url_base = url_base + "/";
381   if (cmd_line->HasSwitch("allowed-ips") ||
382       cmd_line->HasSwitch("whitelisted-ips")) {
383     allow_remote = true;
384     if (cmd_line->HasSwitch("allowed-ips"))
385       allowlist = cmd_line->GetSwitchValueASCII("allowed-ips");
386     else
387       allowlist = cmd_line->GetSwitchValueASCII("whitelisted-ips");
388 
389     std::vector<std::string> allowlist_ip_strs = base::SplitString(
390         allowlist, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
391     if (!allowlist_ip_strs.empty()) {
392       // Convert IP address strings into net::IPAddress objects.
393       for (const auto& ip_str : allowlist_ip_strs) {
394         base::StringPiece ip_str_piece(ip_str);
395         if (ip_str_piece.size() >= 2 && ip_str_piece.front() == '[' &&
396             ip_str_piece.back() == ']') {
397           ip_str_piece.remove_prefix(1);
398           ip_str_piece.remove_suffix(1);
399         }
400         net::IPAddress ip;
401         if (!ip.AssignFromIPLiteral(ip_str_piece)) {
402           printf("Invalid IP address %s. Exiting...\n", ip_str.c_str());
403           return 1;
404         }
405         allowed_ips.push_back(ip);
406         if (ip.IsIPv4()) {
407           allowed_ips.push_back(net::ConvertIPv4ToIPv4MappedIPv6(ip));
408         } else if (ip.IsIPv4MappedIPv6()) {
409           allowed_ips.push_back(net::ConvertIPv4MappedIPv6ToIPv4(ip));
410         }
411       }
412       allowed_ips.push_back(net::IPAddress::IPv4Localhost());
413       allowed_ips.push_back(net::IPAddress::IPv6Localhost());
414       allowed_ips.push_back(
415           net::ConvertIPv4ToIPv4MappedIPv6(net::IPAddress::IPv4Localhost()));
416     }
417   }
418   if (!cmd_line->HasSwitch("silent") &&
419       cmd_line->GetSwitchValueASCII("log-level") != "OFF") {
420     printf("Starting %s %s on port %u\n", kChromeDriverProductShortName,
421            kChromeDriverVersion, port);
422     if (!allow_remote) {
423       printf("Only local connections are allowed.\n");
424     } else if (!allowed_ips.empty()) {
425       printf("Remote connections are allowed by an allowlist (%s).\n",
426              allowlist.c_str());
427     } else {
428       printf("All remote connections are allowed. Use an allowlist instead!\n");
429     }
430     printf("%s\n", GetPortProtectionMessage());
431     fflush(stdout);
432   }
433 
434   if (!InitLogging(port)) {
435     printf("Unable to initialize logging. Exiting...\n");
436     return 1;
437   }
438 
439 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
440   EnsureSharedMemory(cmd_line);
441 #endif
442 
443   mojo::core::Init();
444 
445   base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
446       kChromeDriverProductShortName);
447 
448   RunServer(port, allow_remote, allowed_ips, url_base, adb_port);
449 
450   // clean up
451   base::ThreadPoolInstance::Get()->Shutdown();
452   return 0;
453 }
454