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_t code)152 bool HttpCodeHasBody(uint32_t code) {
153 return !HttpCodeIsInformational(code)
154 && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
155 }
156
HttpCodeIsCacheable(uint32_t code)157 bool HttpCodeIsCacheable(uint32_t 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 #if _MSC_VER >= 1900
391 long timezone = 0;
392 _get_timezone(&timezone);
393 #endif
394 *seconds = gmt - timezone;
395 #endif
396 return true;
397 }
398
HttpAddress(const SocketAddress & address,bool secure)399 std::string HttpAddress(const SocketAddress& address, bool secure) {
400 return (address.port() == HttpDefaultPort(secure))
401 ? address.hostname() : address.ToString();
402 }
403
404 //////////////////////////////////////////////////////////////////////
405 // HttpData
406 //////////////////////////////////////////////////////////////////////
407
HttpData()408 HttpData::HttpData() : version(HVER_1_1) {
409 }
410
411 HttpData::~HttpData() = default;
412
413 void
clear(bool release_document)414 HttpData::clear(bool release_document) {
415 // Clear headers first, since releasing a document may have far-reaching
416 // effects.
417 headers_.clear();
418 if (release_document) {
419 document.reset();
420 }
421 }
422
423 void
copy(const HttpData & src)424 HttpData::copy(const HttpData& src) {
425 headers_ = src.headers_;
426 }
427
428 void
changeHeader(const std::string & name,const std::string & value,HeaderCombine combine)429 HttpData::changeHeader(const std::string& name, const std::string& value,
430 HeaderCombine combine) {
431 if (combine == HC_AUTO) {
432 HttpHeader header;
433 // Unrecognized headers are collapsible
434 combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
435 ? HC_YES : HC_NO;
436 } else if (combine == HC_REPLACE) {
437 headers_.erase(name);
438 combine = HC_NO;
439 }
440 // At this point, combine is one of (YES, NO, NEW)
441 if (combine != HC_NO) {
442 HeaderMap::iterator it = headers_.find(name);
443 if (it != headers_.end()) {
444 if (combine == HC_YES) {
445 it->second.append(",");
446 it->second.append(value);
447 }
448 return;
449 }
450 }
451 headers_.insert(HeaderMap::value_type(name, value));
452 }
453
clearHeader(const std::string & name)454 size_t HttpData::clearHeader(const std::string& name) {
455 return headers_.erase(name);
456 }
457
clearHeader(iterator header)458 HttpData::iterator HttpData::clearHeader(iterator header) {
459 iterator deprecated = header++;
460 headers_.erase(deprecated);
461 return header;
462 }
463
464 bool
hasHeader(const std::string & name,std::string * value) const465 HttpData::hasHeader(const std::string& name, std::string* value) const {
466 HeaderMap::const_iterator it = headers_.find(name);
467 if (it == headers_.end()) {
468 return false;
469 } else if (value) {
470 *value = it->second;
471 }
472 return true;
473 }
474
setContent(const std::string & content_type,StreamInterface * document)475 void HttpData::setContent(const std::string& content_type,
476 StreamInterface* document) {
477 setHeader(HH_CONTENT_TYPE, content_type);
478 setDocumentAndLength(document);
479 }
480
setDocumentAndLength(StreamInterface * document)481 void HttpData::setDocumentAndLength(StreamInterface* document) {
482 // TODO: Consider calling Rewind() here?
483 ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
484 ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
485 ASSERT(document != NULL);
486 this->document.reset(document);
487 size_t content_length = 0;
488 if (this->document->GetAvailable(&content_length)) {
489 char buffer[32];
490 sprintfn(buffer, sizeof(buffer), "%d", content_length);
491 setHeader(HH_CONTENT_LENGTH, buffer);
492 } else {
493 setHeader(HH_TRANSFER_ENCODING, "chunked");
494 }
495 }
496
497 //
498 // HttpRequestData
499 //
500
501 void
clear(bool release_document)502 HttpRequestData::clear(bool release_document) {
503 verb = HV_GET;
504 path.clear();
505 HttpData::clear(release_document);
506 }
507
508 void
copy(const HttpRequestData & src)509 HttpRequestData::copy(const HttpRequestData& src) {
510 verb = src.verb;
511 path = src.path;
512 HttpData::copy(src);
513 }
514
515 size_t
formatLeader(char * buffer,size_t size) const516 HttpRequestData::formatLeader(char* buffer, size_t size) const {
517 ASSERT(path.find(' ') == std::string::npos);
518 return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
519 path.data(), ToString(version));
520 }
521
522 HttpError
parseLeader(const char * line,size_t len)523 HttpRequestData::parseLeader(const char* line, size_t len) {
524 unsigned int vmajor, vminor;
525 int vend, dstart, dend;
526 // sscanf isn't safe with strings that aren't null-terminated, and there is
527 // no guarantee that |line| is. Create a local copy that is null-terminated.
528 std::string line_str(line, len);
529 line = line_str.c_str();
530 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u",
531 &vend, &dstart, &dend, &vmajor, &vminor) != 2)
532 || (vmajor != 1)) {
533 return HE_PROTOCOL;
534 }
535 if (vminor == 0) {
536 version = HVER_1_0;
537 } else if (vminor == 1) {
538 version = HVER_1_1;
539 } else {
540 return HE_PROTOCOL;
541 }
542 std::string sverb(line, vend);
543 if (!FromString(verb, sverb.c_str())) {
544 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
545 }
546 path.assign(line + dstart, line + dend);
547 return HE_NONE;
548 }
549
getAbsoluteUri(std::string * uri) const550 bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
551 if (HV_CONNECT == verb)
552 return false;
553 Url<char> url(path);
554 if (url.valid()) {
555 uri->assign(path);
556 return true;
557 }
558 std::string host;
559 if (!hasHeader(HH_HOST, &host))
560 return false;
561 url.set_address(host);
562 url.set_full_path(path);
563 uri->assign(url.url());
564 return url.valid();
565 }
566
getRelativeUri(std::string * host,std::string * path) const567 bool HttpRequestData::getRelativeUri(std::string* host,
568 std::string* path) const
569 {
570 if (HV_CONNECT == verb)
571 return false;
572 Url<char> url(this->path);
573 if (url.valid()) {
574 host->assign(url.address());
575 path->assign(url.full_path());
576 return true;
577 }
578 if (!hasHeader(HH_HOST, host))
579 return false;
580 path->assign(this->path);
581 return true;
582 }
583
584 //
585 // HttpResponseData
586 //
587
588 void
clear(bool release_document)589 HttpResponseData::clear(bool release_document) {
590 scode = HC_INTERNAL_SERVER_ERROR;
591 message.clear();
592 HttpData::clear(release_document);
593 }
594
595 void
copy(const HttpResponseData & src)596 HttpResponseData::copy(const HttpResponseData& src) {
597 scode = src.scode;
598 message = src.message;
599 HttpData::copy(src);
600 }
601
set_success(uint32_t scode)602 void HttpResponseData::set_success(uint32_t scode) {
603 this->scode = scode;
604 message.clear();
605 setHeader(HH_CONTENT_LENGTH, "0", false);
606 }
607
set_success(const std::string & content_type,StreamInterface * document,uint32_t scode)608 void HttpResponseData::set_success(const std::string& content_type,
609 StreamInterface* document,
610 uint32_t scode) {
611 this->scode = scode;
612 message.erase(message.begin(), message.end());
613 setContent(content_type, document);
614 }
615
set_redirect(const std::string & location,uint32_t scode)616 void HttpResponseData::set_redirect(const std::string& location,
617 uint32_t scode) {
618 this->scode = scode;
619 message.clear();
620 setHeader(HH_LOCATION, location);
621 setHeader(HH_CONTENT_LENGTH, "0", false);
622 }
623
set_error(uint32_t scode)624 void HttpResponseData::set_error(uint32_t scode) {
625 this->scode = scode;
626 message.clear();
627 setHeader(HH_CONTENT_LENGTH, "0", false);
628 }
629
630 size_t
formatLeader(char * buffer,size_t size) const631 HttpResponseData::formatLeader(char* buffer, size_t size) const {
632 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
633 if (!message.empty()) {
634 len += sprintfn(buffer + len, size - len, " %.*s",
635 message.size(), message.data());
636 }
637 return len;
638 }
639
640 HttpError
parseLeader(const char * line,size_t len)641 HttpResponseData::parseLeader(const char* line, size_t len) {
642 size_t pos = 0;
643 unsigned int vmajor, vminor, temp_scode;
644 int temp_pos;
645 // sscanf isn't safe with strings that aren't null-terminated, and there is
646 // no guarantee that |line| is. Create a local copy that is null-terminated.
647 std::string line_str(line, len);
648 line = line_str.c_str();
649 if (sscanf(line, "HTTP %u%n",
650 &temp_scode, &temp_pos) == 1) {
651 // This server's response has no version. :( NOTE: This happens for every
652 // response to requests made from Chrome plugins, regardless of the server's
653 // behaviour.
654 LOG(LS_VERBOSE) << "HTTP version missing from response";
655 version = HVER_UNKNOWN;
656 } else if ((sscanf(line, "HTTP/%u.%u %u%n",
657 &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
658 && (vmajor == 1)) {
659 // This server's response does have a version.
660 if (vminor == 0) {
661 version = HVER_1_0;
662 } else if (vminor == 1) {
663 version = HVER_1_1;
664 } else {
665 return HE_PROTOCOL;
666 }
667 } else {
668 return HE_PROTOCOL;
669 }
670 scode = temp_scode;
671 pos = static_cast<size_t>(temp_pos);
672 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
673 message.assign(line + pos, len - pos);
674 return HE_NONE;
675 }
676
677 //////////////////////////////////////////////////////////////////////
678 // Http Authentication
679 //////////////////////////////////////////////////////////////////////
680
681 #define TEST_DIGEST 0
682 #if TEST_DIGEST
683 /*
684 const char * const DIGEST_CHALLENGE =
685 "Digest realm=\"testrealm@host.com\","
686 " qop=\"auth,auth-int\","
687 " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
688 " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
689 const char * const DIGEST_METHOD = "GET";
690 const char * const DIGEST_URI =
691 "/dir/index.html";;
692 const char * const DIGEST_CNONCE =
693 "0a4f113b";
694 const char * const DIGEST_RESPONSE =
695 "6629fae49393a05397450978507c4ef1";
696 //user_ = "Mufasa";
697 //pass_ = "Circle Of Life";
698 */
699 const char * const DIGEST_CHALLENGE =
700 "Digest realm=\"Squid proxy-caching web server\","
701 " nonce=\"Nny4QuC5PwiSDixJ\","
702 " qop=\"auth\","
703 " stale=false";
704 const char * const DIGEST_URI =
705 "/";
706 const char * const DIGEST_CNONCE =
707 "6501d58e9a21cee1e7b5fec894ded024";
708 const char * const DIGEST_RESPONSE =
709 "edffcb0829e755838b073a4a42de06bc";
710 #endif
711
quote(const std::string & str)712 std::string quote(const std::string& str) {
713 std::string result;
714 result.push_back('"');
715 for (size_t i=0; i<str.size(); ++i) {
716 if ((str[i] == '"') || (str[i] == '\\'))
717 result.push_back('\\');
718 result.push_back(str[i]);
719 }
720 result.push_back('"');
721 return result;
722 }
723
724 #if defined(WEBRTC_WIN)
725 struct NegotiateAuthContext : public HttpAuthContext {
726 CredHandle cred;
727 CtxtHandle ctx;
728 size_t steps;
729 bool specified_credentials;
730
NegotiateAuthContextrtc::NegotiateAuthContext731 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
732 : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
733 specified_credentials(false)
734 { }
735
~NegotiateAuthContextrtc::NegotiateAuthContext736 virtual ~NegotiateAuthContext() {
737 DeleteSecurityContext(&ctx);
738 FreeCredentialsHandle(&cred);
739 }
740 };
741 #endif // WEBRTC_WIN
742
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)743 HttpAuthResult HttpAuthenticate(
744 const char * challenge, size_t len,
745 const SocketAddress& server,
746 const std::string& method, const std::string& uri,
747 const std::string& username, const CryptString& password,
748 HttpAuthContext *& context, std::string& response, std::string& auth_method)
749 {
750 #if TEST_DIGEST
751 challenge = DIGEST_CHALLENGE;
752 len = strlen(challenge);
753 #endif
754
755 HttpAttributeList args;
756 HttpParseAttributes(challenge, len, args);
757 HttpHasNthAttribute(args, 0, &auth_method, NULL);
758
759 if (context && (context->auth_method != auth_method))
760 return HAR_IGNORE;
761
762 // BASIC
763 if (_stricmp(auth_method.c_str(), "basic") == 0) {
764 if (context)
765 return HAR_CREDENTIALS; // Bad credentials
766 if (username.empty())
767 return HAR_CREDENTIALS; // Missing credentials
768
769 context = new HttpAuthContext(auth_method);
770
771 // TODO: convert sensitive to a secure buffer that gets securely deleted
772 //std::string decoded = username + ":" + password;
773 size_t len = username.size() + password.GetLength() + 2;
774 char * sensitive = new char[len];
775 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
776 pos += strcpyn(sensitive + pos, len - pos, ":");
777 password.CopyTo(sensitive + pos, true);
778
779 response = auth_method;
780 response.append(" ");
781 // TODO: create a sensitive-source version of Base64::encode
782 response.append(Base64::Encode(sensitive));
783 memset(sensitive, 0, len);
784 delete [] sensitive;
785 return HAR_RESPONSE;
786 }
787
788 // DIGEST
789 if (_stricmp(auth_method.c_str(), "digest") == 0) {
790 if (context)
791 return HAR_CREDENTIALS; // Bad credentials
792 if (username.empty())
793 return HAR_CREDENTIALS; // Missing credentials
794
795 context = new HttpAuthContext(auth_method);
796
797 std::string cnonce, ncount;
798 #if TEST_DIGEST
799 method = DIGEST_METHOD;
800 uri = DIGEST_URI;
801 cnonce = DIGEST_CNONCE;
802 #else
803 char buffer[256];
804 sprintf(buffer, "%d", static_cast<int>(time(0)));
805 cnonce = MD5(buffer);
806 #endif
807 ncount = "00000001";
808
809 std::string realm, nonce, qop, opaque;
810 HttpHasAttribute(args, "realm", &realm);
811 HttpHasAttribute(args, "nonce", &nonce);
812 bool has_qop = HttpHasAttribute(args, "qop", &qop);
813 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
814
815 // TODO: convert sensitive to be secure buffer
816 //std::string A1 = username + ":" + realm + ":" + password;
817 size_t len = username.size() + realm.size() + password.GetLength() + 3;
818 char * sensitive = new char[len]; // A1
819 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
820 pos += strcpyn(sensitive + pos, len - pos, ":");
821 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
822 pos += strcpyn(sensitive + pos, len - pos, ":");
823 password.CopyTo(sensitive + pos, true);
824
825 std::string A2 = method + ":" + uri;
826 std::string middle;
827 if (has_qop) {
828 qop = "auth";
829 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
830 } else {
831 middle = nonce;
832 }
833 std::string HA1 = MD5(sensitive);
834 memset(sensitive, 0, len);
835 delete [] sensitive;
836 std::string HA2 = MD5(A2);
837 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
838
839 #if TEST_DIGEST
840 ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
841 #endif
842
843 std::stringstream ss;
844 ss << auth_method;
845 ss << " username=" << quote(username);
846 ss << ", realm=" << quote(realm);
847 ss << ", nonce=" << quote(nonce);
848 ss << ", uri=" << quote(uri);
849 if (has_qop) {
850 ss << ", qop=" << qop;
851 ss << ", nc=" << ncount;
852 ss << ", cnonce=" << quote(cnonce);
853 }
854 ss << ", response=\"" << dig_response << "\"";
855 if (has_opaque) {
856 ss << ", opaque=" << quote(opaque);
857 }
858 response = ss.str();
859 return HAR_RESPONSE;
860 }
861
862 #if defined(WEBRTC_WIN)
863 #if 1
864 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
865 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
866 // SPNEGO & NTLM
867 if (want_negotiate || want_ntlm) {
868 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
869 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
870
871 #if 0 // Requires funky windows versions
872 DWORD len = MAX_SPN;
873 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL,
874 server.port(),
875 0, &len, spn) != ERROR_SUCCESS) {
876 LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
877 return HAR_IGNORE;
878 }
879 #else
880 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
881 #endif
882
883 SecBuffer out_sec;
884 out_sec.pvBuffer = out_buf;
885 out_sec.cbBuffer = sizeof(out_buf);
886 out_sec.BufferType = SECBUFFER_TOKEN;
887
888 SecBufferDesc out_buf_desc;
889 out_buf_desc.ulVersion = 0;
890 out_buf_desc.cBuffers = 1;
891 out_buf_desc.pBuffers = &out_sec;
892
893 const ULONG NEG_FLAGS_DEFAULT =
894 //ISC_REQ_ALLOCATE_MEMORY
895 ISC_REQ_CONFIDENTIALITY
896 //| ISC_REQ_EXTENDED_ERROR
897 //| ISC_REQ_INTEGRITY
898 | ISC_REQ_REPLAY_DETECT
899 | ISC_REQ_SEQUENCE_DETECT
900 //| ISC_REQ_STREAM
901 //| ISC_REQ_USE_SUPPLIED_CREDS
902 ;
903
904 ::TimeStamp lifetime;
905 SECURITY_STATUS ret = S_OK;
906 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
907
908 bool specify_credentials = !username.empty();
909 size_t steps = 0;
910
911 // uint32_t now = Time();
912
913 NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
914 if (neg) {
915 const size_t max_steps = 10;
916 if (++neg->steps >= max_steps) {
917 LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
918 return HAR_ERROR;
919 }
920 steps = neg->steps;
921
922 std::string challenge, decoded_challenge;
923 if (HttpHasNthAttribute(args, 1, &challenge, NULL)
924 && Base64::Decode(challenge, Base64::DO_STRICT,
925 &decoded_challenge, NULL)) {
926 SecBuffer in_sec;
927 in_sec.pvBuffer = const_cast<char *>(decoded_challenge.data());
928 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
929 in_sec.BufferType = SECBUFFER_TOKEN;
930
931 SecBufferDesc in_buf_desc;
932 in_buf_desc.ulVersion = 0;
933 in_buf_desc.cBuffers = 1;
934 in_buf_desc.pBuffers = &in_sec;
935
936 ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
937 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
938 if (FAILED(ret)) {
939 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
940 << ErrorName(ret, SECURITY_ERRORS);
941 return HAR_ERROR;
942 }
943 } else if (neg->specified_credentials) {
944 // Try again with default credentials
945 specify_credentials = false;
946 delete context;
947 context = neg = 0;
948 } else {
949 return HAR_CREDENTIALS;
950 }
951 }
952
953 if (!neg) {
954 unsigned char userbuf[256], passbuf[256], domainbuf[16];
955 SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
956 if (specify_credentials) {
957 memset(&auth_id, 0, sizeof(auth_id));
958 size_t len = password.GetLength()+1;
959 char * sensitive = new char[len];
960 password.CopyTo(sensitive, true);
961 std::string::size_type pos = username.find('\\');
962 if (pos == std::string::npos) {
963 auth_id.UserLength = static_cast<unsigned long>(
964 std::min(sizeof(userbuf) - 1, username.size()));
965 memcpy(userbuf, username.c_str(), auth_id.UserLength);
966 userbuf[auth_id.UserLength] = 0;
967 auth_id.DomainLength = 0;
968 domainbuf[auth_id.DomainLength] = 0;
969 auth_id.PasswordLength = static_cast<unsigned long>(
970 std::min(sizeof(passbuf) - 1, password.GetLength()));
971 memcpy(passbuf, sensitive, auth_id.PasswordLength);
972 passbuf[auth_id.PasswordLength] = 0;
973 } else {
974 auth_id.UserLength = static_cast<unsigned long>(
975 std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
976 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
977 userbuf[auth_id.UserLength] = 0;
978 auth_id.DomainLength =
979 static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
980 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
981 domainbuf[auth_id.DomainLength] = 0;
982 auth_id.PasswordLength = static_cast<unsigned long>(
983 std::min(sizeof(passbuf) - 1, password.GetLength()));
984 memcpy(passbuf, sensitive, auth_id.PasswordLength);
985 passbuf[auth_id.PasswordLength] = 0;
986 }
987 memset(sensitive, 0, len);
988 delete [] sensitive;
989 auth_id.User = userbuf;
990 auth_id.Domain = domainbuf;
991 auth_id.Password = passbuf;
992 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
993 pauth_id = &auth_id;
994 LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
995 } else {
996 LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
997 }
998
999 CredHandle cred;
1000 ret = AcquireCredentialsHandleA(
1001 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
1002 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
1003 //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
1004 if (ret != SEC_E_OK) {
1005 LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
1006 << ErrorName(ret, SECURITY_ERRORS);
1007 return HAR_IGNORE;
1008 }
1009
1010 //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
1011
1012 CtxtHandle ctx;
1013 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
1014 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
1015 if (FAILED(ret)) {
1016 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
1017 << ErrorName(ret, SECURITY_ERRORS);
1018 FreeCredentialsHandle(&cred);
1019 return HAR_IGNORE;
1020 }
1021
1022 ASSERT(!context);
1023 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
1024 neg->specified_credentials = specify_credentials;
1025 neg->steps = steps;
1026 }
1027
1028 if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
1029 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
1030 //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
1031 LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
1032 << ErrorName(ret, SECURITY_ERRORS);
1033 if (FAILED(ret)) {
1034 return HAR_ERROR;
1035 }
1036 }
1037
1038 //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
1039
1040 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1041 response = auth_method;
1042 response.append(" ");
1043 response.append(Base64::Encode(decoded));
1044 return HAR_RESPONSE;
1045 }
1046 #endif
1047 #endif // WEBRTC_WIN
1048
1049 return HAR_IGNORE;
1050 }
1051
1052 //////////////////////////////////////////////////////////////////////
1053
1054 } // namespace rtc
1055