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 35namespace rtc { 36 37#if defined(WEBRTC_WIN) 38extern const ConstantLabel SECURITY_ERRORS[]; 39#endif 40 41////////////////////////////////////////////////////////////////////// 42// Enum - TODO: expose globally later? 43////////////////////////////////////////////////////////////////////// 44 45bool 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 55template<class E> 56struct Enum { 57 static const char** Names; 58 static size_t Size; 59 60 static inline const char* Name(E val) { return Names[val]; } 61 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 71 inline operator E&() { return val; } 72 inline Enum& operator=(E rhs) { val = rhs; return *this; } 73 74 inline const char* name() const { return Name(val); } 75 inline bool assign(const std::string& name) { return Parse(val, name); } 76 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 87static const char* kHttpVersions[HVER_LAST+1] = { 88 "1.0", "1.1", "Unknown" 89}; 90ENUM(HttpVersion, kHttpVersions); 91 92static const char* kHttpVerbs[HV_LAST+1] = { 93 "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD" 94}; 95ENUM(HttpVerb, kHttpVerbs); 96 97static 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}; 127ENUM(HttpHeader, kHttpHeaders); 128 129const char* ToString(HttpVersion version) { 130 return Enum<HttpVersion>::Name(version); 131} 132 133bool FromString(HttpVersion& version, const std::string& str) { 134 return Enum<HttpVersion>::Parse(version, str); 135} 136 137const char* ToString(HttpVerb verb) { 138 return Enum<HttpVerb>::Name(verb); 139} 140 141bool FromString(HttpVerb& verb, const std::string& str) { 142 return Enum<HttpVerb>::Parse(verb, str); 143} 144 145const char* ToString(HttpHeader header) { 146 return Enum<HttpHeader>::Name(header); 147} 148 149bool FromString(HttpHeader& header, const std::string& str) { 150 return Enum<HttpHeader>::Parse(header, str); 151} 152 153bool HttpCodeHasBody(uint32_t code) { 154 return !HttpCodeIsInformational(code) 155 && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED); 156} 157 158bool 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 172bool 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 189bool 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 200bool 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 209namespace { 210 211inline 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 228std::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 238void 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 253void 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 303bool 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 318bool 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 332bool 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 400std::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 409HttpData::HttpData() : version(HVER_1_1) { 410} 411 412HttpData::~HttpData() = default; 413 414void 415HttpData::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 424void 425HttpData::copy(const HttpData& src) { 426 headers_ = src.headers_; 427} 428 429void 430HttpData::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 455size_t HttpData::clearHeader(const std::string& name) { 456 return headers_.erase(name); 457} 458 459HttpData::iterator HttpData::clearHeader(iterator header) { 460 iterator deprecated = header++; 461 headers_.erase(deprecated); 462 return header; 463} 464 465bool 466HttpData::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 476void HttpData::setContent(const std::string& content_type, 477 StreamInterface* document) { 478 setHeader(HH_CONTENT_TYPE, content_type); 479 setDocumentAndLength(document); 480} 481 482void 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 502void 503HttpRequestData::clear(bool release_document) { 504 verb = HV_GET; 505 path.clear(); 506 HttpData::clear(release_document); 507} 508 509void 510HttpRequestData::copy(const HttpRequestData& src) { 511 verb = src.verb; 512 path = src.path; 513 HttpData::copy(src); 514} 515 516size_t 517HttpRequestData::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 523HttpError 524HttpRequestData::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 551bool 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 568bool 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 589void 590HttpResponseData::clear(bool release_document) { 591 scode = HC_INTERNAL_SERVER_ERROR; 592 message.clear(); 593 HttpData::clear(release_document); 594} 595 596void 597HttpResponseData::copy(const HttpResponseData& src) { 598 scode = src.scode; 599 message = src.message; 600 HttpData::copy(src); 601} 602 603void HttpResponseData::set_success(uint32_t scode) { 604 this->scode = scode; 605 message.clear(); 606 setHeader(HH_CONTENT_LENGTH, "0", false); 607} 608 609void 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 617void 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 625void HttpResponseData::set_error(uint32_t scode) { 626 this->scode = scode; 627 message.clear(); 628 setHeader(HH_CONTENT_LENGTH, "0", false); 629} 630 631size_t 632HttpResponseData::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 641HttpError 642HttpResponseData::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/* 685const char * const DIGEST_CHALLENGE = 686 "Digest realm=\"testrealm@host.com\"," 687 " qop=\"auth,auth-int\"," 688 " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," 689 " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""; 690const char * const DIGEST_METHOD = "GET"; 691const char * const DIGEST_URI = 692 "/dir/index.html";; 693const char * const DIGEST_CNONCE = 694 "0a4f113b"; 695const char * const DIGEST_RESPONSE = 696 "6629fae49393a05397450978507c4ef1"; 697//user_ = "Mufasa"; 698//pass_ = "Circle Of Life"; 699*/ 700const char * const DIGEST_CHALLENGE = 701 "Digest realm=\"Squid proxy-caching web server\"," 702 " nonce=\"Nny4QuC5PwiSDixJ\"," 703 " qop=\"auth\"," 704 " stale=false"; 705const char * const DIGEST_URI = 706 "/"; 707const char * const DIGEST_CNONCE = 708 "6501d58e9a21cee1e7b5fec894ded024"; 709const char * const DIGEST_RESPONSE = 710 "edffcb0829e755838b073a4a42de06bc"; 711#endif 712 713std::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) 726struct NegotiateAuthContext : public HttpAuthContext { 727 CredHandle cred; 728 CtxtHandle ctx; 729 size_t steps; 730 bool specified_credentials; 731 732 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2) 733 : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0), 734 specified_credentials(false) 735 { } 736 737 virtual ~NegotiateAuthContext() { 738 DeleteSecurityContext(&ctx); 739 FreeCredentialsHandle(&cred); 740 } 741}; 742#endif // WEBRTC_WIN 743 744HttpAuthResult 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