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