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