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