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