1/*
2 * libjingle
3 * Copyright 2004--2005, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/base/autodetectproxy.h"
29#include "talk/base/httpcommon.h"
30#include "talk/base/httpcommon-inl.h"
31#include "talk/base/nethelpers.h"
32
33namespace talk_base {
34
35static const ProxyType TEST_ORDER[] = {
36  PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
37};
38
39static const int kSavedStringLimit = 128;
40
41static void SaveStringToStack(char *dst,
42                              const std::string &src,
43                              size_t dst_size) {
44  strncpy(dst, src.c_str(), dst_size - 1);
45  dst[dst_size - 1] = '\0';
46}
47
48AutoDetectProxy::AutoDetectProxy(const std::string& user_agent)
49    : agent_(user_agent), resolver_(NULL), socket_(NULL), next_(0) {
50}
51
52AutoDetectProxy::~AutoDetectProxy() {
53  if (resolver_) {
54    resolver_->Destroy(false);
55  }
56}
57
58void AutoDetectProxy::DoWork() {
59  // TODO: Try connecting to server_url without proxy first here?
60  if (!server_url_.empty()) {
61    LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start";
62    GetProxyForUrl(agent_.c_str(), server_url_.c_str(), &proxy_);
63    LOG(LS_INFO) << "GetProxySettingsForUrl - stop";
64  }
65  Url<char> url(proxy_.address.HostAsURIString());
66  if (url.valid()) {
67    LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host";
68    proxy_.address.SetIP(url.host());
69  }
70  LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address;
71  if (proxy_.type == PROXY_UNKNOWN) {
72    LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification";
73    Next();
74    // Process I/O until Stop()
75    Thread::Current()->ProcessMessages(kForever);
76    // Clean up the autodetect socket, from the thread that created it
77    delete socket_;
78  }
79  // TODO: If we found a proxy, try to use it to verify that it
80  // works by sending a request to server_url. This could either be
81  // done here or by the HttpPortAllocator.
82}
83
84void AutoDetectProxy::OnMessage(Message *msg) {
85  if (MSG_UNRESOLVABLE == msg->message_id) {
86    // If we can't resolve the proxy, skip straight to failure.
87    Complete(PROXY_UNKNOWN);
88  } else if (MSG_TIMEOUT == msg->message_id) {
89    OnCloseEvent(socket_, ETIMEDOUT);
90  } else {
91    // This must be the ST_MSG_WORKER_DONE message that deletes the
92    // AutoDetectProxy object. We have observed crashes within this stack that
93    // seem to be highly reproducible for a small subset of users and thus are
94    // probably correlated with a specific proxy setting, so copy potentially
95    // relevant information onto the stack to make it available in Windows
96    // minidumps.
97
98    // Save the user agent and the number of auto-detection passes that we
99    // needed.
100    char agent[kSavedStringLimit];
101    SaveStringToStack(agent, agent_, sizeof agent);
102
103    int next = next_;
104
105    // Now the detected proxy config (minus the password field, which could be
106    // sensitive).
107    ProxyType type = proxy().type;
108
109    char address_hostname[kSavedStringLimit];
110    SaveStringToStack(address_hostname,
111                      proxy().address.hostname(),
112                      sizeof address_hostname);
113
114    IPAddress address_ip = proxy().address.ipaddr();
115
116    uint16 address_port = proxy().address.port();
117
118    char autoconfig_url[kSavedStringLimit];
119    SaveStringToStack(autoconfig_url,
120                      proxy().autoconfig_url,
121                      sizeof autoconfig_url);
122
123    bool autodetect = proxy().autodetect;
124
125    char bypass_list[kSavedStringLimit];
126    SaveStringToStack(bypass_list, proxy().bypass_list, sizeof bypass_list);
127
128    char username[kSavedStringLimit];
129    SaveStringToStack(username, proxy().username, sizeof username);
130
131    SignalThread::OnMessage(msg);
132
133    // Log the gathered data at a log level that will never actually be enabled
134    // so that the compiler is forced to retain the data on the stack.
135    LOG(LS_SENSITIVE) << agent << " " << next << " " << type << " "
136                      << address_hostname << " " << address_ip << " "
137                      << address_port << " " << autoconfig_url << " "
138                      << autodetect << " " << bypass_list << " " << username;
139  }
140}
141
142void AutoDetectProxy::OnResolveResult(AsyncResolverInterface* resolver) {
143  if (resolver != resolver_) {
144    return;
145  }
146  int error = resolver_->GetError();
147  if (error == 0) {
148    LOG(LS_VERBOSE) << "Resolved " << proxy_.address << " to "
149                    << resolver_->address();
150    proxy_.address = resolver_->address();
151    if (!DoConnect()) {
152      Thread::Current()->Post(this, MSG_TIMEOUT);
153    }
154  } else {
155    LOG(LS_INFO) << "Failed to resolve " << resolver_->address();
156    resolver_->Destroy(false);
157    resolver_ = NULL;
158    proxy_.address = SocketAddress();
159    Thread::Current()->Post(this, MSG_UNRESOLVABLE);
160  }
161}
162
163void AutoDetectProxy::Next() {
164  if (TEST_ORDER[next_] >= PROXY_UNKNOWN) {
165    Complete(PROXY_UNKNOWN);
166    return;
167  }
168
169  LOG(LS_VERBOSE) << "AutoDetectProxy connecting to "
170                  << proxy_.address.ToSensitiveString();
171
172  if (socket_) {
173    Thread::Current()->Clear(this, MSG_TIMEOUT);
174    Thread::Current()->Clear(this, MSG_UNRESOLVABLE);
175    socket_->Close();
176    Thread::Current()->Dispose(socket_);
177    socket_ = NULL;
178  }
179  int timeout = 2000;
180  if (proxy_.address.IsUnresolvedIP()) {
181    // Launch an asyncresolver. This thread will spin waiting for it.
182    timeout += 2000;
183    if (!resolver_) {
184      resolver_ = new AsyncResolver();
185    }
186    resolver_->SignalDone.connect(this, &AutoDetectProxy::OnResolveResult);
187    resolver_->Start(proxy_.address);
188  } else {
189    if (!DoConnect()) {
190      Thread::Current()->Post(this, MSG_TIMEOUT);
191      return;
192    }
193  }
194  Thread::Current()->PostDelayed(timeout, this, MSG_TIMEOUT);
195}
196
197bool AutoDetectProxy::DoConnect() {
198  if (resolver_) {
199    resolver_->Destroy(false);
200    resolver_ = NULL;
201  }
202  socket_ =
203      Thread::Current()->socketserver()->CreateAsyncSocket(
204          proxy_.address.family(), SOCK_STREAM);
205  if (!socket_) {
206    LOG(LS_VERBOSE) << "Unable to create socket for " << proxy_.address;
207    return false;
208  }
209  socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent);
210  socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent);
211  socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent);
212  socket_->Connect(proxy_.address);
213  return true;
214}
215
216void AutoDetectProxy::Complete(ProxyType type) {
217  Thread::Current()->Clear(this, MSG_TIMEOUT);
218  Thread::Current()->Clear(this, MSG_UNRESOLVABLE);
219  if (socket_) {
220    socket_->Close();
221  }
222
223  proxy_.type = type;
224  LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO;
225  LOG_V(sev) << "AutoDetectProxy detected "
226             << proxy_.address.ToSensitiveString()
227             << " as type " << proxy_.type;
228
229  Thread::Current()->Quit();
230}
231
232void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) {
233  std::string probe;
234
235  switch (TEST_ORDER[next_]) {
236    case PROXY_HTTPS:
237      probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n"
238                   "User-Agent: ");
239      probe.append(agent_);
240      probe.append("\r\n"
241                   "Host: www.google.com\r\n"
242                   "Content-Length: 0\r\n"
243                   "Proxy-Connection: Keep-Alive\r\n"
244                   "\r\n");
245      break;
246    case PROXY_SOCKS5:
247      probe.assign("\005\001\000", 3);
248      break;
249    default:
250      ASSERT(false);
251      return;
252  }
253
254  LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_]
255                  << " sending " << probe.size() << " bytes";
256  socket_->Send(probe.data(), probe.size());
257}
258
259void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) {
260  char data[257];
261  int len = socket_->Recv(data, 256);
262  if (len > 0) {
263    data[len] = 0;
264    LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes";
265  }
266
267  switch (TEST_ORDER[next_]) {
268    case PROXY_HTTPS:
269      if ((len >= 2) && (data[0] == '\x05')) {
270        Complete(PROXY_SOCKS5);
271        return;
272      }
273      if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) {
274        Complete(PROXY_HTTPS);
275        return;
276      }
277      break;
278    case PROXY_SOCKS5:
279      if ((len >= 2) && (data[0] == '\x05')) {
280        Complete(PROXY_SOCKS5);
281        return;
282      }
283      break;
284    default:
285      ASSERT(false);
286      return;
287  }
288
289  ++next_;
290  Next();
291}
292
293void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) {
294  LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error;
295  ++next_;
296  Next();
297}
298
299}  // namespace talk_base
300