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