blob: a4b56c66ff360b46ee44637007ac7ef3aaaebb85 [file] [log] [blame]
Andreea Costinase45d54b2020-03-10 09:21:14 +01001// Copyright 2020 The Chromium OS 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#ifndef SYSTEM_PROXY_PROXY_CONNECT_JOB_H_
5#define SYSTEM_PROXY_PROXY_CONNECT_JOB_H_
6
7#include <list>
8#include <memory>
9#include <string>
10#include <string_view>
11#include <vector>
12
Andreea Costinasf90a4c02020-06-12 22:30:51 +020013#include <curl/curl.h>
14
Andreea Costinase45d54b2020-03-10 09:21:14 +010015#include <base/callback_forward.h>
Andreea Costinas08a5d182020-04-29 22:12:47 +020016#include <base/cancelable_callback.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010017#include <base/files/file_descriptor_watcher_posix.h>
18#include <gtest/gtest_prod.h> // for FRIEND_TEST
19
Garrick Evans3388a032020-03-24 11:25:55 +090020namespace patchpanel {
Andreea Costinase45d54b2020-03-10 09:21:14 +010021class SocketForwarder;
22class Socket;
Garrick Evans3388a032020-03-24 11:25:55 +090023} // namespace patchpanel
Andreea Costinase45d54b2020-03-10 09:21:14 +010024
25namespace system_proxy {
26// ProxyConnectJob asynchronously sets up a connection to a remote target on
Andreea Costinasbb2aa022020-06-13 00:03:23 +020027// behalf of a client using the following steps:
28// 1. Gets the target url from the client request;
29// 2. Asks the parent to resolve the proxy for the target url via
30// |resolve_proxy_callback_|;
31// 3. Connects to the target url trough the remote proxy server returned by the
32// parent.
33// 3. 1. On success, it will return a SocketForwarder to the parent, which
34// forwards data between the Chrome OS client and the remote server.
35// 3. 2. On error, it will check the HTTP status code from the server's reply.
36// 3. 2. 1. If the status code means credentials are required, it asks the
37// parent for authentication credentials via |auth_required_callback|.
38// - If the parent returns credentials for the proxy server challenge,
39// it attempts to reconnect (step 3);
40// - Otherwise it will forward the status code to the client.
41// 3.2.1.2. Other status codes are forwarded to the Chrome OS clients and the
42// connection is closed.
43// Note: Reconnecting to the server with credentials (step 3. 2. 1.) will create
44// a new connection to the remote server, while the connection to the local
45// client is still open and in a waiting state during the authentication
46// process.
47// TODO(acostinas,crbug.com/1098200): Cancel the proxy connect job if the
48// request for credentials is not resolved after a certain time.
Andreea Costinase45d54b2020-03-10 09:21:14 +010049class ProxyConnectJob {
50 public:
51 using OnConnectionSetupFinishedCallback = base::OnceCallback<void(
Garrick Evans3388a032020-03-24 11:25:55 +090052 std::unique_ptr<patchpanel::SocketForwarder>, ProxyConnectJob*)>;
Andreea Costinase45d54b2020-03-10 09:21:14 +010053
54 // Will be invoked by ProxyConnectJob to resolve the proxy for |target_url_|.
55 // The passed |callback| is expected to be called with the list of proxy
56 // servers, which will always contain at least one entry, the default proxy.
57 using ResolveProxyCallback = base::OnceCallback<void(
58 const std::string& url,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020059 base::OnceCallback<void(const std::list<std::string>&)>
60 on_proxy_resolution_callback)>;
61 // Will be invoked by ProxyConnectJob to request the credentials for requests
62 // that fail with code 407.
63 using AuthenticationRequiredCallback = base::OnceCallback<void(
64 const std::string& proxy_url,
65 const std::string& scheme,
66 const std::string& realm,
67 base::OnceCallback<void(const std::string& credentials)>
68 on_auth_acquired_callback)>;
Andreea Costinase45d54b2020-03-10 09:21:14 +010069
Garrick Evans3388a032020-03-24 11:25:55 +090070 ProxyConnectJob(std::unique_ptr<patchpanel::Socket> socket,
Andreea Costinase45d54b2020-03-10 09:21:14 +010071 const std::string& credentials,
72 ResolveProxyCallback resolve_proxy_callback,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020073 AuthenticationRequiredCallback auth_required_callback,
Andreea Costinase45d54b2020-03-10 09:21:14 +010074 OnConnectionSetupFinishedCallback setup_finished_callback);
75 ProxyConnectJob(const ProxyConnectJob&) = delete;
76 ProxyConnectJob& operator=(const ProxyConnectJob&) = delete;
77 virtual ~ProxyConnectJob();
78
79 // Marks |client_socket_| as non-blocking and adds a watcher that calls
80 // |OnClientReadReady| when the socket is read ready.
81 virtual bool Start();
82 void OnProxyResolution(const std::list<std::string>& proxy_servers);
83
84 friend std::ostream& operator<<(std::ostream& stream,
85 const ProxyConnectJob& job);
86
87 private:
88 friend class ServerProxyTest;
89 friend class ProxyConnectJobTest;
90 FRIEND_TEST(ServerProxyTest, HandlePendingJobs);
Andreea Costinas5862b102020-03-19 14:45:36 +010091 FRIEND_TEST(ServerProxyTest, HandleConnectRequest);
Andreea Costinase45d54b2020-03-10 09:21:14 +010092 FRIEND_TEST(ProxyConnectJobTest, SuccessfulConnection);
Andreea Costinased3f9782020-05-20 17:09:46 +020093 FRIEND_TEST(ProxyConnectJobTest, SuccessfulConnectionAltEnding);
Andreea Costinase45d54b2020-03-10 09:21:14 +010094 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestWrongMethod);
95 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestNoEmptyLine);
Andreea Costinasbb2aa022020-06-13 00:03:23 +020096 FRIEND_TEST(ProxyConnectJobTest, ResendWithCredentials);
97 FRIEND_TEST(ProxyConnectJobTest, NoCredentials);
98 FRIEND_TEST(ProxyConnectJobTest, KerberosAuth);
Andreea Costinase45d54b2020-03-10 09:21:14 +010099
100 // Reads data from the socket into |raw_request| until the first empty line,
101 // which would mark the end of the HTTP request header.
102 // This method does not validate the headers.
103 bool TryReadHttpHeader(std::vector<char>* raw_request);
104
105 // Called when the client socket is ready for reading.
106 void OnClientReadReady();
107
108 // Called from |OnProxyResolution|, after the proxy for |target_url_| is
109 // resolved.
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200110 void DoCurlServerConnection();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100111
112 void OnError(const std::string_view& http_error_message);
113
Andreea Costinas08a5d182020-04-29 22:12:47 +0200114 void OnClientConnectTimeout();
115
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200116 // Requests the credentials to authenticate to the remote proxy server. The
117 // credentials are bound to the protection space extracted from the challenge
118 // sent by the remote server.
119 void AuthenticationRequired(const std::vector<char>& http_response_headers);
120 void OnAuthCredentialsProvided(const std::string& credentials);
121 // Checks if the HTTP CONNECT request has failed because of missing proxy
122 // authentication credentials.
123 bool AreAuthCredentialsRequired(CURL* easyhandle);
124
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200125 // Sends the server response to the client. Returns true if the headers and
126 // body were sent successfully, false otherwise. In case of failure, calls
127 // |OnError|. The response headers and body can be empty if the libcurl
128 // connection fails. In this case, this will send the client an error message
129 // based on the HTTP status code |http_response_code_|.
130 bool SendHttpResponseToClient(const std::vector<char>& http_response_headers,
131 const std::vector<char>& http_response_body);
132
Andreea Costinase45d54b2020-03-10 09:21:14 +0100133 std::string target_url_;
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200134 // HTTP proxy response code to the CONNECT request.
135 int64_t http_response_code_ = 0;
136
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200137 std::string credentials_;
138 // Keep track on whether this is the first connect attempt or not. If a proxy
139 // connection attempt fails because of missing or invalid credentials, the
140 // code will attempt to reconnect with credentials fetched from the parent
141 // process. We should only reconnect after the first failed try otherwise
142 // the code will go into an infinite loop when fetched credentials are
143 // invalid.
144 bool first_http_connect_attempt_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100145 std::list<std::string> proxy_servers_;
146 ResolveProxyCallback resolve_proxy_callback_;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200147 AuthenticationRequiredCallback auth_required_callback_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100148 OnConnectionSetupFinishedCallback setup_finished_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200149 base::CancelableClosure client_connect_timeout_callback_;
150
Garrick Evans3388a032020-03-24 11:25:55 +0900151 std::unique_ptr<patchpanel::Socket> client_socket_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100152 std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200153 base::WeakPtrFactory<ProxyConnectJob> weak_ptr_factory_{this};
Andreea Costinase45d54b2020-03-10 09:21:14 +0100154};
155} // namespace system_proxy
156
157#endif // SYSTEM_PROXY_PROXY_CONNECT_JOB_H_