1 // Copyright (c) 2012 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 "net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h"
6 
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/memory/free_deleter.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/task_runner.h"
15 #include "base/threading/scoped_blocking_call.h"
16 #include "base/time/time.h"
17 #include "net/base/net_errors.h"
18 #include "net/proxy_resolution/dhcpcsvc_init_win.h"
19 #include "net/proxy_resolution/pac_file_fetcher_impl.h"
20 #include "net/url_request/url_request_context.h"
21 
22 #include <windows.h>
23 #include <winsock2.h>
24 #include <dhcpcsdk.h>
25 
26 namespace {
27 
28 // Maximum amount of time to wait for response from the Win32 DHCP API.
29 const int kTimeoutMs = 2000;
30 
31 }  // namespace
32 
33 namespace net {
34 
DhcpPacFileAdapterFetcher(URLRequestContext * url_request_context,scoped_refptr<base::TaskRunner> task_runner)35 DhcpPacFileAdapterFetcher::DhcpPacFileAdapterFetcher(
36     URLRequestContext* url_request_context,
37     scoped_refptr<base::TaskRunner> task_runner)
38     : task_runner_(task_runner),
39       state_(STATE_START),
40       result_(ERR_IO_PENDING),
41       url_request_context_(url_request_context) {
42   DCHECK(url_request_context_);
43 }
44 
~DhcpPacFileAdapterFetcher()45 DhcpPacFileAdapterFetcher::~DhcpPacFileAdapterFetcher() {
46   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
47   Cancel();
48 }
49 
Fetch(const std::string & adapter_name,CompletionOnceCallback callback,const NetworkTrafficAnnotationTag traffic_annotation)50 void DhcpPacFileAdapterFetcher::Fetch(
51     const std::string& adapter_name,
52     CompletionOnceCallback callback,
53     const NetworkTrafficAnnotationTag traffic_annotation) {
54   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
55   DCHECK_EQ(state_, STATE_START);
56   result_ = ERR_IO_PENDING;
57   pac_script_ = base::string16();
58   state_ = STATE_WAIT_DHCP;
59   callback_ = std::move(callback);
60 
61   wait_timer_.Start(FROM_HERE, ImplGetTimeout(), this,
62                     &DhcpPacFileAdapterFetcher::OnTimeout);
63   scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
64   task_runner_->PostTaskAndReply(
65       FROM_HERE,
66       base::BindOnce(&DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
67                      dhcp_query.get(), adapter_name),
68       base::BindOnce(&DhcpPacFileAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(),
69                      dhcp_query, traffic_annotation));
70 }
71 
Cancel()72 void DhcpPacFileAdapterFetcher::Cancel() {
73   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
74   callback_.Reset();
75   wait_timer_.Stop();
76   script_fetcher_.reset();
77 
78   switch (state_) {
79     case STATE_WAIT_DHCP:
80       // Nothing to do here, we let the worker thread run to completion,
81       // the task it posts back when it completes will check the state.
82       break;
83     case STATE_WAIT_URL:
84       break;
85     case STATE_START:
86     case STATE_FINISH:
87     case STATE_CANCEL:
88       break;
89   }
90 
91   if (state_ != STATE_FINISH) {
92     result_ = ERR_ABORTED;
93     state_ = STATE_CANCEL;
94   }
95 }
96 
DidFinish() const97 bool DhcpPacFileAdapterFetcher::DidFinish() const {
98   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
99   return state_ == STATE_FINISH;
100 }
101 
GetResult() const102 int DhcpPacFileAdapterFetcher::GetResult() const {
103   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
104   return result_;
105 }
106 
GetPacScript() const107 base::string16 DhcpPacFileAdapterFetcher::GetPacScript() const {
108   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
109   return pac_script_;
110 }
111 
GetPacURL() const112 GURL DhcpPacFileAdapterFetcher::GetPacURL() const {
113   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
114   return pac_url_;
115 }
116 
DhcpQuery()117 DhcpPacFileAdapterFetcher::DhcpQuery::DhcpQuery() {}
118 
GetPacURLForAdapter(const std::string & adapter_name)119 void DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
120     const std::string& adapter_name) {
121   url_ = ImplGetPacURLFromDhcp(adapter_name);
122 }
123 
url() const124 const std::string& DhcpPacFileAdapterFetcher::DhcpQuery::url() const {
125   return url_;
126 }
127 
ImplGetPacURLFromDhcp(const std::string & adapter_name)128 std::string DhcpPacFileAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
129     const std::string& adapter_name) {
130   return DhcpPacFileAdapterFetcher::GetPacURLFromDhcp(adapter_name);
131 }
132 
~DhcpQuery()133 DhcpPacFileAdapterFetcher::DhcpQuery::~DhcpQuery() {}
134 
OnDhcpQueryDone(scoped_refptr<DhcpQuery> dhcp_query,const NetworkTrafficAnnotationTag traffic_annotation)135 void DhcpPacFileAdapterFetcher::OnDhcpQueryDone(
136     scoped_refptr<DhcpQuery> dhcp_query,
137     const NetworkTrafficAnnotationTag traffic_annotation) {
138   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
139   // Because we can't cancel the call to the Win32 API, we can expect
140   // it to finish while we are in a few different states.  The expected
141   // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
142   // or FINISH if timeout occurred.
143   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
144          state_ == STATE_FINISH);
145   if (state_ != STATE_WAIT_DHCP)
146     return;
147 
148   wait_timer_.Stop();
149 
150   pac_url_ = GURL(dhcp_query->url());
151   if (pac_url_.is_empty() || !pac_url_.is_valid()) {
152     result_ = ERR_PAC_NOT_IN_DHCP;
153     TransitionToFinish();
154   } else {
155     state_ = STATE_WAIT_URL;
156     script_fetcher_ = ImplCreateScriptFetcher();
157     script_fetcher_->Fetch(
158         pac_url_, &pac_script_,
159         base::BindOnce(&DhcpPacFileAdapterFetcher::OnFetcherDone,
160                        base::Unretained(this)),
161         traffic_annotation);
162   }
163 }
164 
OnTimeout()165 void DhcpPacFileAdapterFetcher::OnTimeout() {
166   DCHECK_EQ(state_, STATE_WAIT_DHCP);
167   result_ = ERR_TIMED_OUT;
168   TransitionToFinish();
169 }
170 
OnFetcherDone(int result)171 void DhcpPacFileAdapterFetcher::OnFetcherDone(int result) {
172   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
173   DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
174   if (state_ == STATE_CANCEL)
175     return;
176 
177   // At this point, pac_script_ has already been written to.
178   script_fetcher_.reset();
179   result_ = result;
180   TransitionToFinish();
181 }
182 
TransitionToFinish()183 void DhcpPacFileAdapterFetcher::TransitionToFinish() {
184   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
185   state_ = STATE_FINISH;
186 
187   // Be careful not to touch any member state after this, as the client
188   // may delete us during this callback.
189   std::move(callback_).Run(result_);
190 }
191 
state() const192 DhcpPacFileAdapterFetcher::State DhcpPacFileAdapterFetcher::state() const {
193   return state_;
194 }
195 
196 std::unique_ptr<PacFileFetcher>
ImplCreateScriptFetcher()197 DhcpPacFileAdapterFetcher::ImplCreateScriptFetcher() {
198   return PacFileFetcherImpl::Create(url_request_context_);
199 }
200 
201 DhcpPacFileAdapterFetcher::DhcpQuery*
ImplCreateDhcpQuery()202 DhcpPacFileAdapterFetcher::ImplCreateDhcpQuery() {
203   return new DhcpQuery();
204 }
205 
ImplGetTimeout() const206 base::TimeDelta DhcpPacFileAdapterFetcher::ImplGetTimeout() const {
207   return base::TimeDelta::FromMilliseconds(kTimeoutMs);
208 }
209 
210 // static
GetPacURLFromDhcp(const std::string & adapter_name)211 std::string DhcpPacFileAdapterFetcher::GetPacURLFromDhcp(
212     const std::string& adapter_name) {
213   EnsureDhcpcsvcInit();
214 
215   std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
216                                                             CP_ACP);
217 
218   DHCPCAPI_PARAMS_ARRAY send_params = {0, nullptr};
219 
220   DHCPCAPI_PARAMS wpad_params = { 0 };
221   wpad_params.OptionId = 252;
222   wpad_params.IsVendor = FALSE;  // Surprising, but intentional.
223 
224   DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
225   request_params.nParams = 1;
226   request_params.Params = &wpad_params;
227 
228   // The maximum message size is typically 4096 bytes on Windows per
229   // http://support.microsoft.com/kb/321592
230   DWORD result_buffer_size = 4096;
231   std::unique_ptr<BYTE, base::FreeDeleter> result_buffer;
232   int retry_count = 0;
233   DWORD res = NO_ERROR;
234   do {
235     result_buffer.reset(static_cast<BYTE*>(malloc(result_buffer_size)));
236 
237     // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
238     // there might be an asynchronous mode, there seems to be (at least in
239     // terms of well-documented use of this API) only a synchronous mode, with
240     // an optional "async notifications later if the option changes" mode.
241     // Even IE9, which we hope to emulate as IE is the most widely deployed
242     // previous implementation of the DHCP aspect of WPAD and the only one
243     // on Windows (Konqueror is the other, on Linux), uses this API with the
244     // synchronous flag.  There seem to be several Microsoft Knowledge Base
245     // articles about calls to this function failing when other flags are used
246     // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
247     // chances on non-standard, poorly documented usage.
248     base::ScopedBlockingCall scoped_blocking_call(
249         FROM_HERE, base::BlockingType::MAY_BLOCK);
250     res = ::DhcpRequestParams(
251         DHCPCAPI_REQUEST_SYNCHRONOUS, nullptr,
252         const_cast<LPWSTR>(adapter_name_wide.c_str()), nullptr, send_params,
253         request_params, result_buffer.get(), &result_buffer_size, nullptr);
254     ++retry_count;
255   } while (res == ERROR_MORE_DATA && retry_count <= 3);
256 
257   if (res != NO_ERROR) {
258     VLOG(1) << "Error fetching PAC URL from DHCP: " << res;
259   } else if (wpad_params.nBytesData) {
260     return SanitizeDhcpApiString(
261         reinterpret_cast<const char*>(wpad_params.Data),
262         wpad_params.nBytesData);
263   }
264 
265   return "";
266 }
267 
268 // static
SanitizeDhcpApiString(const char * data,size_t count_bytes)269 std::string DhcpPacFileAdapterFetcher::SanitizeDhcpApiString(
270     const char* data,
271     size_t count_bytes) {
272   // The result should be ASCII, not wide character.  Some DHCP
273   // servers appear to count the trailing NULL in nBytesData, others
274   // do not.  A few (we've had one report, http://crbug.com/297810)
275   // do not NULL-terminate but may \n-terminate.
276   //
277   // Belt and suspenders and elastic waistband: First, ensure we
278   // NULL-terminate after nBytesData; this is the inner constructor
279   // with nBytesData as a parameter.  Then, return only up to the
280   // first null in case of embedded NULLs; this is the outer
281   // constructor that takes the result of c_str() on the inner.  If
282   // the server is giving us back a buffer with embedded NULLs,
283   // something is broken anyway.  Finally, trim trailing whitespace.
284   std::string result(std::string(data, count_bytes).c_str());
285   base::TrimWhitespaceASCII(result, base::TRIM_TRAILING, &result);
286   return result;
287 }
288 
289 }  // namespace net
290