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