blob: 561d2f49257aeaf5cee1b875c8148cd9d86adc36 [file] [log] [blame]
henrike@webrtc.org47be73b2014-05-13 18:00:261/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <time.h>
12
13#if defined(WEBRTC_WIN)
14#define WIN32_LEAN_AND_MEAN
15#include <windows.h>
16#include <winsock2.h>
17#include <ws2tcpip.h>
18#define SECURITY_WIN32
19#include <security.h>
20#endif
21
22#include "webrtc/base/httpcommon-inl.h"
23
24#include "webrtc/base/base64.h"
25#include "webrtc/base/common.h"
26#include "webrtc/base/cryptstring.h"
27#include "webrtc/base/httpcommon.h"
28#include "webrtc/base/socketaddress.h"
29#include "webrtc/base/stringdigest.h"
30#include "webrtc/base/stringencode.h"
31#include "webrtc/base/stringutils.h"
32
33namespace rtc {
34
35#if defined(WEBRTC_WIN)
36extern const ConstantLabel SECURITY_ERRORS[];
37#endif
38
39//////////////////////////////////////////////////////////////////////
40// Enum - TODO: expose globally later?
41//////////////////////////////////////////////////////////////////////
42
43bool find_string(size_t& index, const std::string& needle,
44 const char* const haystack[], size_t max_index) {
45 for (index=0; index<max_index; ++index) {
sergeyu@chromium.orgb957cd82014-07-25 19:42:1946 if (_stricmp(needle.c_str(), haystack[index]) == 0) {
47 return true;
48 }
henrike@webrtc.org47be73b2014-05-13 18:00:2649 }
50 return false;
51}
52
53template<class E>
54struct Enum {
55 static const char** Names;
56 static size_t Size;
57
58 static inline const char* Name(E val) { return Names[val]; }
59 static inline bool Parse(E& val, const std::string& name) {
sergeyu@chromium.orgb957cd82014-07-25 19:42:1960 size_t index;
61 if (!find_string(index, name, Names, Size))
62 return false;
63 val = static_cast<E>(index);
64 return true;
henrike@webrtc.org47be73b2014-05-13 18:00:2665 }
66
67 E val;
68
69 inline operator E&() { return val; }
70 inline Enum& operator=(E rhs) { val = rhs; return *this; }
71
72 inline const char* name() const { return Name(val); }
73 inline bool assign(const std::string& name) { return Parse(val, name); }
74 inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; }
75};
76
77#define ENUM(e,n) \
78 template<> const char** Enum<e>::Names = n; \
79 template<> size_t Enum<e>::Size = sizeof(n)/sizeof(n[0])
80
81//////////////////////////////////////////////////////////////////////
82// HttpCommon
83//////////////////////////////////////////////////////////////////////
84
85static const char* kHttpVersions[HVER_LAST+1] = {
86 "1.0", "1.1", "Unknown"
87};
88ENUM(HttpVersion, kHttpVersions);
89
90static const char* kHttpVerbs[HV_LAST+1] = {
91 "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
92};
93ENUM(HttpVerb, kHttpVerbs);
94
95static const char* kHttpHeaders[HH_LAST+1] = {
96 "Age",
97 "Cache-Control",
98 "Connection",
99 "Content-Disposition",
100 "Content-Length",
101 "Content-Range",
102 "Content-Type",
103 "Cookie",
104 "Date",
105 "ETag",
106 "Expires",
107 "Host",
108 "If-Modified-Since",
109 "If-None-Match",
110 "Keep-Alive",
111 "Last-Modified",
112 "Location",
113 "Proxy-Authenticate",
114 "Proxy-Authorization",
115 "Proxy-Connection",
116 "Range",
117 "Set-Cookie",
118 "TE",
119 "Trailers",
120 "Transfer-Encoding",
121 "Upgrade",
122 "User-Agent",
123 "WWW-Authenticate",
124};
125ENUM(HttpHeader, kHttpHeaders);
126
127const char* ToString(HttpVersion version) {
128 return Enum<HttpVersion>::Name(version);
129}
130
131bool FromString(HttpVersion& version, const std::string& str) {
132 return Enum<HttpVersion>::Parse(version, str);
133}
134
135const char* ToString(HttpVerb verb) {
136 return Enum<HttpVerb>::Name(verb);
137}
138
139bool FromString(HttpVerb& verb, const std::string& str) {
140 return Enum<HttpVerb>::Parse(verb, str);
141}
142
143const char* ToString(HttpHeader header) {
144 return Enum<HttpHeader>::Name(header);
145}
146
147bool FromString(HttpHeader& header, const std::string& str) {
148 return Enum<HttpHeader>::Parse(header, str);
149}
150
151bool HttpCodeHasBody(uint32 code) {
152 return !HttpCodeIsInformational(code)
153 && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
154}
155
156bool HttpCodeIsCacheable(uint32 code) {
157 switch (code) {
158 case HC_OK:
159 case HC_NON_AUTHORITATIVE:
160 case HC_PARTIAL_CONTENT:
161 case HC_MULTIPLE_CHOICES:
162 case HC_MOVED_PERMANENTLY:
163 case HC_GONE:
164 return true;
165 default:
166 return false;
167 }
168}
169
170bool HttpHeaderIsEndToEnd(HttpHeader header) {
171 switch (header) {
172 case HH_CONNECTION:
173 case HH_KEEP_ALIVE:
174 case HH_PROXY_AUTHENTICATE:
175 case HH_PROXY_AUTHORIZATION:
176 case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard header
177 case HH_TE:
178 case HH_TRAILERS:
179 case HH_TRANSFER_ENCODING:
180 case HH_UPGRADE:
181 return false;
182 default:
183 return true;
184 }
185}
186
187bool HttpHeaderIsCollapsible(HttpHeader header) {
188 switch (header) {
189 case HH_SET_COOKIE:
190 case HH_PROXY_AUTHENTICATE:
191 case HH_WWW_AUTHENTICATE:
192 return false;
193 default:
194 return true;
195 }
196}
197
198bool HttpShouldKeepAlive(const HttpData& data) {
199 std::string connection;
200 if ((data.hasHeader(HH_PROXY_CONNECTION, &connection)
201 || data.hasHeader(HH_CONNECTION, &connection))) {
202 return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
203 }
204 return (data.version >= HVER_1_1);
205}
206
207namespace {
208
209inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) {
210 if (pos >= len)
211 return true;
212 if (isspace(static_cast<unsigned char>(data[pos])))
213 return true;
214 // The reason for this complexity is that some attributes may contain trailing
215 // equal signs (like base64 tokens in Negotiate auth headers)
216 if ((pos+1 < len) && (data[pos] == '=') &&
217 !isspace(static_cast<unsigned char>(data[pos+1])) &&
218 (data[pos+1] != '=')) {
219 return true;
220 }
221 return false;
222}
223
224// TODO: unittest for EscapeAttribute and HttpComposeAttributes.
225
226std::string EscapeAttribute(const std::string& attribute) {
227 const size_t kMaxLength = attribute.length() * 2 + 1;
228 char* buffer = STACK_ARRAY(char, kMaxLength);
229 size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(),
230 "\"", '\\');
231 return std::string(buffer, len);
232}
233
234} // anonymous namespace
235
236void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
237 std::string* composed) {
238 std::stringstream ss;
239 for (size_t i=0; i<attributes.size(); ++i) {
240 if (i > 0) {
241 ss << separator << " ";
242 }
243 ss << attributes[i].first;
244 if (!attributes[i].second.empty()) {
245 ss << "=\"" << EscapeAttribute(attributes[i].second) << "\"";
246 }
247 }
248 *composed = ss.str();
249}
250
251void HttpParseAttributes(const char * data, size_t len,
252 HttpAttributeList& attributes) {
253 size_t pos = 0;
254 while (true) {
255 // Skip leading whitespace
256 while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
257 ++pos;
258 }
259
260 // End of attributes?
261 if (pos >= len)
262 return;
263
264 // Find end of attribute name
265 size_t start = pos;
266 while (!IsEndOfAttributeName(pos, len, data)) {
267 ++pos;
268 }
269
270 HttpAttribute attribute;
271 attribute.first.assign(data + start, data + pos);
272
273 // Attribute has value?
274 if ((pos < len) && (data[pos] == '=')) {
275 ++pos; // Skip '='
276 // Check if quoted value
277 if ((pos < len) && (data[pos] == '"')) {
278 while (++pos < len) {
279 if (data[pos] == '"') {
280 ++pos;
281 break;
282 }
283 if ((data[pos] == '\\') && (pos + 1 < len))
284 ++pos;
285 attribute.second.append(1, data[pos]);
286 }
287 } else {
288 while ((pos < len) &&
289 !isspace(static_cast<unsigned char>(data[pos])) &&
290 (data[pos] != ',')) {
291 attribute.second.append(1, data[pos++]);
292 }
293 }
294 }
295
296 attributes.push_back(attribute);
297 if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ','
298 }
299}
300
301bool HttpHasAttribute(const HttpAttributeList& attributes,
302 const std::string& name,
303 std::string* value) {
304 for (HttpAttributeList::const_iterator it = attributes.begin();
305 it != attributes.end(); ++it) {
306 if (it->first == name) {
307 if (value) {
308 *value = it->second;
309 }
310 return true;
311 }
312 }
313 return false;
314}
315
316bool HttpHasNthAttribute(HttpAttributeList& attributes,
317 size_t index,
318 std::string* name,
319 std::string* value) {
320 if (index >= attributes.size())
321 return false;
322
323 if (name)
324 *name = attributes[index].first;
325 if (value)
326 *value = attributes[index].second;
327 return true;
328}
329
330bool HttpDateToSeconds(const std::string& date, time_t* seconds) {
331 const char* const kTimeZones[] = {
332 "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
333 "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
334 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"
335 };
336 const int kTimeZoneOffsets[] = {
337 0, 0, -5, -4, -6, -5, -7, -6, -8, -7,
338 -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
339 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
340 };
341
342 ASSERT(NULL != seconds);
343 struct tm tval;
344 memset(&tval, 0, sizeof(tval));
345 char month[4], zone[6];
346 memset(month, 0, sizeof(month));
347 memset(zone, 0, sizeof(zone));
348
349 if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c",
350 &tval.tm_mday, month, &tval.tm_year,
351 &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) {
352 return false;
353 }
354 switch (toupper(month[2])) {
355 case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break;
356 case 'B': tval.tm_mon = 1; break;
357 case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break;
358 case 'Y': tval.tm_mon = 4; break;
359 case 'L': tval.tm_mon = 6; break;
360 case 'G': tval.tm_mon = 7; break;
361 case 'P': tval.tm_mon = 8; break;
362 case 'T': tval.tm_mon = 9; break;
363 case 'V': tval.tm_mon = 10; break;
364 case 'C': tval.tm_mon = 11; break;
365 }
366 tval.tm_year -= 1900;
henrike@webrtc.orgc2e66142014-09-10 22:10:24367 time_t gmt, non_gmt = mktime(&tval);
henrike@webrtc.org47be73b2014-05-13 18:00:26368 if ((zone[0] == '+') || (zone[0] == '-')) {
369 if (!isdigit(zone[1]) || !isdigit(zone[2])
370 || !isdigit(zone[3]) || !isdigit(zone[4])) {
371 return false;
372 }
373 int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
374 int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
375 int offset = (hours * 60 + minutes) * 60;
376 gmt = non_gmt + ((zone[0] == '+') ? offset : -offset);
377 } else {
378 size_t zindex;
379 if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) {
380 return false;
381 }
382 gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
383 }
384 // TODO: Android should support timezone, see b/2441195
385#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || defined(BSD)
henrike@webrtc.orgc2e66142014-09-10 22:10:24386 tm *tm_for_timezone = localtime(&gmt);
henrike@webrtc.org47be73b2014-05-13 18:00:26387 *seconds = gmt + tm_for_timezone->tm_gmtoff;
388#else
389 *seconds = gmt - timezone;
390#endif
391 return true;
392}
393
394std::string HttpAddress(const SocketAddress& address, bool secure) {
395 return (address.port() == HttpDefaultPort(secure))
396 ? address.hostname() : address.ToString();
397}
398
399//////////////////////////////////////////////////////////////////////
400// HttpData
401//////////////////////////////////////////////////////////////////////
402
403void
404HttpData::clear(bool release_document) {
405 // Clear headers first, since releasing a document may have far-reaching
406 // effects.
407 headers_.clear();
408 if (release_document) {
409 document.reset();
410 }
411}
412
413void
414HttpData::copy(const HttpData& src) {
415 headers_ = src.headers_;
416}
417
418void
419HttpData::changeHeader(const std::string& name, const std::string& value,
420 HeaderCombine combine) {
421 if (combine == HC_AUTO) {
422 HttpHeader header;
423 // Unrecognized headers are collapsible
424 combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
425 ? HC_YES : HC_NO;
426 } else if (combine == HC_REPLACE) {
427 headers_.erase(name);
428 combine = HC_NO;
429 }
430 // At this point, combine is one of (YES, NO, NEW)
431 if (combine != HC_NO) {
432 HeaderMap::iterator it = headers_.find(name);
433 if (it != headers_.end()) {
434 if (combine == HC_YES) {
435 it->second.append(",");
436 it->second.append(value);
sergeyu@chromium.orgb957cd82014-07-25 19:42:19437 }
henrike@webrtc.org47be73b2014-05-13 18:00:26438 return;
sergeyu@chromium.orgb957cd82014-07-25 19:42:19439 }
henrike@webrtc.org47be73b2014-05-13 18:00:26440 }
441 headers_.insert(HeaderMap::value_type(name, value));
442}
443
444size_t HttpData::clearHeader(const std::string& name) {
445 return headers_.erase(name);
446}
447
448HttpData::iterator HttpData::clearHeader(iterator header) {
449 iterator deprecated = header++;
450 headers_.erase(deprecated);
451 return header;
452}
453
454bool
455HttpData::hasHeader(const std::string& name, std::string* value) const {
456 HeaderMap::const_iterator it = headers_.find(name);
457 if (it == headers_.end()) {
458 return false;
459 } else if (value) {
460 *value = it->second;
461 }
462 return true;
463}
464
465void HttpData::setContent(const std::string& content_type,
466 StreamInterface* document) {
467 setHeader(HH_CONTENT_TYPE, content_type);
468 setDocumentAndLength(document);
469}
470
471void HttpData::setDocumentAndLength(StreamInterface* document) {
472 // TODO: Consider calling Rewind() here?
473 ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
474 ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
475 ASSERT(document != NULL);
476 this->document.reset(document);
477 size_t content_length = 0;
478 if (this->document->GetAvailable(&content_length)) {
479 char buffer[32];
480 sprintfn(buffer, sizeof(buffer), "%d", content_length);
481 setHeader(HH_CONTENT_LENGTH, buffer);
482 } else {
483 setHeader(HH_TRANSFER_ENCODING, "chunked");
484 }
485}
486
487//
488// HttpRequestData
489//
490
491void
492HttpRequestData::clear(bool release_document) {
493 verb = HV_GET;
494 path.clear();
495 HttpData::clear(release_document);
496}
497
498void
499HttpRequestData::copy(const HttpRequestData& src) {
500 verb = src.verb;
501 path = src.path;
502 HttpData::copy(src);
503}
504
505size_t
506HttpRequestData::formatLeader(char* buffer, size_t size) const {
507 ASSERT(path.find(' ') == std::string::npos);
508 return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
509 path.data(), ToString(version));
510}
511
512HttpError
513HttpRequestData::parseLeader(const char* line, size_t len) {
514 unsigned int vmajor, vminor;
515 int vend, dstart, dend;
516 // sscanf isn't safe with strings that aren't null-terminated, and there is
517 // no guarantee that |line| is. Create a local copy that is null-terminated.
518 std::string line_str(line, len);
519 line = line_str.c_str();
520 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u",
521 &vend, &dstart, &dend, &vmajor, &vminor) != 2)
522 || (vmajor != 1)) {
523 return HE_PROTOCOL;
524 }
525 if (vminor == 0) {
526 version = HVER_1_0;
527 } else if (vminor == 1) {
528 version = HVER_1_1;
529 } else {
530 return HE_PROTOCOL;
531 }
532 std::string sverb(line, vend);
533 if (!FromString(verb, sverb.c_str())) {
534 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
535 }
536 path.assign(line + dstart, line + dend);
537 return HE_NONE;
538}
539
540bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
541 if (HV_CONNECT == verb)
542 return false;
543 Url<char> url(path);
544 if (url.valid()) {
545 uri->assign(path);
546 return true;
547 }
548 std::string host;
549 if (!hasHeader(HH_HOST, &host))
550 return false;
551 url.set_address(host);
552 url.set_full_path(path);
553 uri->assign(url.url());
554 return url.valid();
555}
556
557bool HttpRequestData::getRelativeUri(std::string* host,
558 std::string* path) const
559{
560 if (HV_CONNECT == verb)
561 return false;
562 Url<char> url(this->path);
563 if (url.valid()) {
564 host->assign(url.address());
565 path->assign(url.full_path());
566 return true;
567 }
568 if (!hasHeader(HH_HOST, host))
569 return false;
570 path->assign(this->path);
571 return true;
572}
573
574//
575// HttpResponseData
576//
577
578void
579HttpResponseData::clear(bool release_document) {
580 scode = HC_INTERNAL_SERVER_ERROR;
581 message.clear();
582 HttpData::clear(release_document);
583}
584
585void
586HttpResponseData::copy(const HttpResponseData& src) {
587 scode = src.scode;
588 message = src.message;
589 HttpData::copy(src);
590}
591
592void
593HttpResponseData::set_success(uint32 scode) {
594 this->scode = scode;
595 message.clear();
596 setHeader(HH_CONTENT_LENGTH, "0", false);
597}
598
599void
600HttpResponseData::set_success(const std::string& content_type,
601 StreamInterface* document,
602 uint32 scode) {
603 this->scode = scode;
604 message.erase(message.begin(), message.end());
605 setContent(content_type, document);
606}
607
608void
609HttpResponseData::set_redirect(const std::string& location, uint32 scode) {
610 this->scode = scode;
611 message.clear();
612 setHeader(HH_LOCATION, location);
613 setHeader(HH_CONTENT_LENGTH, "0", false);
614}
615
616void
617HttpResponseData::set_error(uint32 scode) {
618 this->scode = scode;
619 message.clear();
620 setHeader(HH_CONTENT_LENGTH, "0", false);
621}
622
623size_t
624HttpResponseData::formatLeader(char* buffer, size_t size) const {
625 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
626 if (!message.empty()) {
627 len += sprintfn(buffer + len, size - len, " %.*s",
628 message.size(), message.data());
629 }
630 return len;
631}
632
633HttpError
634HttpResponseData::parseLeader(const char* line, size_t len) {
635 size_t pos = 0;
636 unsigned int vmajor, vminor, temp_scode;
637 int temp_pos;
638 // sscanf isn't safe with strings that aren't null-terminated, and there is
639 // no guarantee that |line| is. Create a local copy that is null-terminated.
640 std::string line_str(line, len);
641 line = line_str.c_str();
642 if (sscanf(line, "HTTP %u%n",
643 &temp_scode, &temp_pos) == 1) {
644 // This server's response has no version. :( NOTE: This happens for every
645 // response to requests made from Chrome plugins, regardless of the server's
646 // behaviour.
647 LOG(LS_VERBOSE) << "HTTP version missing from response";
648 version = HVER_UNKNOWN;
649 } else if ((sscanf(line, "HTTP/%u.%u %u%n",
650 &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
651 && (vmajor == 1)) {
652 // This server's response does have a version.
653 if (vminor == 0) {
654 version = HVER_1_0;
655 } else if (vminor == 1) {
656 version = HVER_1_1;
657 } else {
658 return HE_PROTOCOL;
659 }
660 } else {
661 return HE_PROTOCOL;
662 }
663 scode = temp_scode;
664 pos = static_cast<size_t>(temp_pos);
665 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
666 message.assign(line + pos, len - pos);
667 return HE_NONE;
668}
669
670//////////////////////////////////////////////////////////////////////
671// Http Authentication
672//////////////////////////////////////////////////////////////////////
673
674#define TEST_DIGEST 0
675#if TEST_DIGEST
676/*
677const char * const DIGEST_CHALLENGE =
678 "Digest realm=\"testrealm@host.com\","
679 " qop=\"auth,auth-int\","
680 " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
681 " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
682const char * const DIGEST_METHOD = "GET";
683const char * const DIGEST_URI =
684 "/dir/index.html";;
685const char * const DIGEST_CNONCE =
686 "0a4f113b";
687const char * const DIGEST_RESPONSE =
688 "6629fae49393a05397450978507c4ef1";
689//user_ = "Mufasa";
690//pass_ = "Circle Of Life";
691*/
692const char * const DIGEST_CHALLENGE =
693 "Digest realm=\"Squid proxy-caching web server\","
694 " nonce=\"Nny4QuC5PwiSDixJ\","
695 " qop=\"auth\","
696 " stale=false";
697const char * const DIGEST_URI =
698 "/";
699const char * const DIGEST_CNONCE =
700 "6501d58e9a21cee1e7b5fec894ded024";
701const char * const DIGEST_RESPONSE =
702 "edffcb0829e755838b073a4a42de06bc";
703#endif
704
705std::string quote(const std::string& str) {
706 std::string result;
707 result.push_back('"');
708 for (size_t i=0; i<str.size(); ++i) {
709 if ((str[i] == '"') || (str[i] == '\\'))
710 result.push_back('\\');
711 result.push_back(str[i]);
712 }
713 result.push_back('"');
714 return result;
715}
716
717#if defined(WEBRTC_WIN)
718struct NegotiateAuthContext : public HttpAuthContext {
719 CredHandle cred;
720 CtxtHandle ctx;
721 size_t steps;
722 bool specified_credentials;
723
724 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
725 : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
726 specified_credentials(false)
727 { }
728
729 virtual ~NegotiateAuthContext() {
730 DeleteSecurityContext(&ctx);
731 FreeCredentialsHandle(&cred);
732 }
733};
sergeyu@chromium.orgb957cd82014-07-25 19:42:19734#endif // WEBRTC_WIN
henrike@webrtc.org47be73b2014-05-13 18:00:26735
736HttpAuthResult HttpAuthenticate(
737 const char * challenge, size_t len,
738 const SocketAddress& server,
739 const std::string& method, const std::string& uri,
740 const std::string& username, const CryptString& password,
741 HttpAuthContext *& context, std::string& response, std::string& auth_method)
742{
743#if TEST_DIGEST
744 challenge = DIGEST_CHALLENGE;
745 len = strlen(challenge);
746#endif
747
748 HttpAttributeList args;
749 HttpParseAttributes(challenge, len, args);
750 HttpHasNthAttribute(args, 0, &auth_method, NULL);
751
752 if (context && (context->auth_method != auth_method))
753 return HAR_IGNORE;
754
755 // BASIC
756 if (_stricmp(auth_method.c_str(), "basic") == 0) {
757 if (context)
758 return HAR_CREDENTIALS; // Bad credentials
759 if (username.empty())
760 return HAR_CREDENTIALS; // Missing credentials
761
762 context = new HttpAuthContext(auth_method);
763
764 // TODO: convert sensitive to a secure buffer that gets securely deleted
765 //std::string decoded = username + ":" + password;
766 size_t len = username.size() + password.GetLength() + 2;
767 char * sensitive = new char[len];
768 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
769 pos += strcpyn(sensitive + pos, len - pos, ":");
770 password.CopyTo(sensitive + pos, true);
771
772 response = auth_method;
773 response.append(" ");
774 // TODO: create a sensitive-source version of Base64::encode
775 response.append(Base64::Encode(sensitive));
776 memset(sensitive, 0, len);
777 delete [] sensitive;
778 return HAR_RESPONSE;
779 }
780
781 // DIGEST
782 if (_stricmp(auth_method.c_str(), "digest") == 0) {
783 if (context)
784 return HAR_CREDENTIALS; // Bad credentials
785 if (username.empty())
786 return HAR_CREDENTIALS; // Missing credentials
787
788 context = new HttpAuthContext(auth_method);
789
790 std::string cnonce, ncount;
791#if TEST_DIGEST
792 method = DIGEST_METHOD;
793 uri = DIGEST_URI;
794 cnonce = DIGEST_CNONCE;
795#else
796 char buffer[256];
797 sprintf(buffer, "%d", static_cast<int>(time(0)));
798 cnonce = MD5(buffer);
799#endif
800 ncount = "00000001";
801
802 std::string realm, nonce, qop, opaque;
803 HttpHasAttribute(args, "realm", &realm);
804 HttpHasAttribute(args, "nonce", &nonce);
805 bool has_qop = HttpHasAttribute(args, "qop", &qop);
806 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
807
808 // TODO: convert sensitive to be secure buffer
809 //std::string A1 = username + ":" + realm + ":" + password;
810 size_t len = username.size() + realm.size() + password.GetLength() + 3;
811 char * sensitive = new char[len]; // A1
812 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
813 pos += strcpyn(sensitive + pos, len - pos, ":");
814 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
815 pos += strcpyn(sensitive + pos, len - pos, ":");
816 password.CopyTo(sensitive + pos, true);
817
818 std::string A2 = method + ":" + uri;
819 std::string middle;
820 if (has_qop) {
821 qop = "auth";
822 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
823 } else {
824 middle = nonce;
825 }
826 std::string HA1 = MD5(sensitive);
827 memset(sensitive, 0, len);
828 delete [] sensitive;
829 std::string HA2 = MD5(A2);
830 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
831
832#if TEST_DIGEST
833 ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
834#endif
835
836 std::stringstream ss;
837 ss << auth_method;
838 ss << " username=" << quote(username);
839 ss << ", realm=" << quote(realm);
840 ss << ", nonce=" << quote(nonce);
841 ss << ", uri=" << quote(uri);
842 if (has_qop) {
843 ss << ", qop=" << qop;
844 ss << ", nc=" << ncount;
845 ss << ", cnonce=" << quote(cnonce);
846 }
847 ss << ", response=\"" << dig_response << "\"";
848 if (has_opaque) {
849 ss << ", opaque=" << quote(opaque);
850 }
851 response = ss.str();
852 return HAR_RESPONSE;
853 }
854
855#if defined(WEBRTC_WIN)
856#if 1
857 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
858 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
859 // SPNEGO & NTLM
860 if (want_negotiate || want_ntlm) {
861 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
862 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
863
864#if 0 // Requires funky windows versions
865 DWORD len = MAX_SPN;
866 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL,
867 server.port(),
868 0, &len, spn) != ERROR_SUCCESS) {
869 LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
870 return HAR_IGNORE;
871 }
872#else
873 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
874#endif
875
876 SecBuffer out_sec;
877 out_sec.pvBuffer = out_buf;
878 out_sec.cbBuffer = sizeof(out_buf);
879 out_sec.BufferType = SECBUFFER_TOKEN;
880
881 SecBufferDesc out_buf_desc;
882 out_buf_desc.ulVersion = 0;
883 out_buf_desc.cBuffers = 1;
884 out_buf_desc.pBuffers = &out_sec;
885
886 const ULONG NEG_FLAGS_DEFAULT =
887 //ISC_REQ_ALLOCATE_MEMORY
888 ISC_REQ_CONFIDENTIALITY
889 //| ISC_REQ_EXTENDED_ERROR
890 //| ISC_REQ_INTEGRITY
891 | ISC_REQ_REPLAY_DETECT
892 | ISC_REQ_SEQUENCE_DETECT
893 //| ISC_REQ_STREAM
894 //| ISC_REQ_USE_SUPPLIED_CREDS
895 ;
896
897 ::TimeStamp lifetime;
898 SECURITY_STATUS ret = S_OK;
899 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
900
901 bool specify_credentials = !username.empty();
902 size_t steps = 0;
903
904 //uint32 now = Time();
905
906 NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
907 if (neg) {
908 const size_t max_steps = 10;
909 if (++neg->steps >= max_steps) {
910 LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
911 return HAR_ERROR;
912 }
913 steps = neg->steps;
914
915 std::string challenge, decoded_challenge;
916 if (HttpHasNthAttribute(args, 1, &challenge, NULL)
917 && Base64::Decode(challenge, Base64::DO_STRICT,
918 &decoded_challenge, NULL)) {
919 SecBuffer in_sec;
920 in_sec.pvBuffer = const_cast<char *>(decoded_challenge.data());
921 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
922 in_sec.BufferType = SECBUFFER_TOKEN;
923
924 SecBufferDesc in_buf_desc;
925 in_buf_desc.ulVersion = 0;
926 in_buf_desc.cBuffers = 1;
927 in_buf_desc.pBuffers = &in_sec;
928
929 ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
930 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
931 if (FAILED(ret)) {
932 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
933 << ErrorName(ret, SECURITY_ERRORS);
934 return HAR_ERROR;
935 }
936 } else if (neg->specified_credentials) {
937 // Try again with default credentials
938 specify_credentials = false;
939 delete context;
940 context = neg = 0;
941 } else {
942 return HAR_CREDENTIALS;
943 }
944 }
945
946 if (!neg) {
947 unsigned char userbuf[256], passbuf[256], domainbuf[16];
948 SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
949 if (specify_credentials) {
950 memset(&auth_id, 0, sizeof(auth_id));
951 size_t len = password.GetLength()+1;
952 char * sensitive = new char[len];
953 password.CopyTo(sensitive, true);
954 std::string::size_type pos = username.find('\\');
955 if (pos == std::string::npos) {
956 auth_id.UserLength = static_cast<unsigned long>(
957 _min(sizeof(userbuf) - 1, username.size()));
958 memcpy(userbuf, username.c_str(), auth_id.UserLength);
959 userbuf[auth_id.UserLength] = 0;
960 auth_id.DomainLength = 0;
961 domainbuf[auth_id.DomainLength] = 0;
962 auth_id.PasswordLength = static_cast<unsigned long>(
963 _min(sizeof(passbuf) - 1, password.GetLength()));
964 memcpy(passbuf, sensitive, auth_id.PasswordLength);
965 passbuf[auth_id.PasswordLength] = 0;
966 } else {
967 auth_id.UserLength = static_cast<unsigned long>(
968 _min(sizeof(userbuf) - 1, username.size() - pos - 1));
969 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
970 userbuf[auth_id.UserLength] = 0;
971 auth_id.DomainLength = static_cast<unsigned long>(
972 _min(sizeof(domainbuf) - 1, pos));
973 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
974 domainbuf[auth_id.DomainLength] = 0;
975 auth_id.PasswordLength = static_cast<unsigned long>(
976 _min(sizeof(passbuf) - 1, password.GetLength()));
977 memcpy(passbuf, sensitive, auth_id.PasswordLength);
978 passbuf[auth_id.PasswordLength] = 0;
979 }
980 memset(sensitive, 0, len);
981 delete [] sensitive;
982 auth_id.User = userbuf;
983 auth_id.Domain = domainbuf;
984 auth_id.Password = passbuf;
985 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
986 pauth_id = &auth_id;
987 LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
988 } else {
989 LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
990 }
991
992 CredHandle cred;
sergeyu@chromium.orgb957cd82014-07-25 19:42:19993 ret = AcquireCredentialsHandleA(
994 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
995 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
henrike@webrtc.org47be73b2014-05-13 18:00:26996 //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
997 if (ret != SEC_E_OK) {
998 LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
999 << ErrorName(ret, SECURITY_ERRORS);
1000 return HAR_IGNORE;
1001 }
1002
1003 //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
1004
1005 CtxtHandle ctx;
1006 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
1007 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
1008 if (FAILED(ret)) {
1009 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
1010 << ErrorName(ret, SECURITY_ERRORS);
1011 FreeCredentialsHandle(&cred);
1012 return HAR_IGNORE;
1013 }
1014
1015 ASSERT(!context);
1016 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
1017 neg->specified_credentials = specify_credentials;
1018 neg->steps = steps;
1019 }
1020
1021 if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
1022 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
1023 //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
1024 LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
1025 << ErrorName(ret, SECURITY_ERRORS);
1026 if (FAILED(ret)) {
1027 return HAR_ERROR;
1028 }
1029 }
1030
1031 //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
1032
1033 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1034 response = auth_method;
1035 response.append(" ");
1036 response.append(Base64::Encode(decoded));
1037 return HAR_RESPONSE;
1038 }
1039#endif
sergeyu@chromium.orgb957cd82014-07-25 19:42:191040#endif // WEBRTC_WIN
henrike@webrtc.org47be73b2014-05-13 18:00:261041
1042 return HAR_IGNORE;
1043}
1044
1045//////////////////////////////////////////////////////////////////////
1046
1047} // namespace rtc