X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/860ff9593b6d706136518693b2a82db6b769e17e..191f736c1fa737e0513e8e00c736b8488ee87f7c:/methods/https.cc diff --git a/methods/https.cc b/methods/https.cc index b758e4ab3..c1a49ba60 100644 --- a/methods/https.cc +++ b/methods/https.cc @@ -1,19 +1,23 @@ -// -*- mode: cpp; mode: fold -*- +//-*- mode: cpp; mode: fold -*- // Description /*{{{*/ // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $ /* ###################################################################### - HTTPS Aquire Method - This is the HTTPS aquire method for APT. + HTTPS Acquire Method - This is the HTTPS aquire method for APT. It uses libcurl ##################################################################### */ /*}}}*/ // Include Files /*{{{*/ +#include + #include #include #include #include +#include +#include #include #include @@ -24,12 +28,11 @@ #include #include #include -#include #include #include "config.h" #include "https.h" - +#include /*}}}*/ using namespace std; @@ -50,107 +53,154 @@ HttpsMethod::progress_callback(void *clientp, double dltotal, double dlnow, { HttpsMethod *me = (HttpsMethod *)clientp; if(dltotal > 0 && me->Res.Size == 0) { - me->Res.Size = dltotal; + me->Res.Size = (unsigned long long)dltotal; me->URIStart(me->Res); } return 0; } -bool HttpsMethod::SetupProxy() +void HttpsMethod::SetupProxy() /*{{{*/ { URI ServerName = Queue->Uri; - // Determine the proxy setting - if (getenv("http_proxy") == 0) - { - string DefProxy = _config->Find("Acquire::http::Proxy"); - string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host); - if (SpecificProxy.empty() == false) - { - if (SpecificProxy == "DIRECT") - Proxy = ""; - else - Proxy = SpecificProxy; - } - else - Proxy = DefProxy; - } - - // Parse no_proxy, a , separated list of domains - if (getenv("no_proxy") != 0) + // Determine the proxy setting - try https first, fallback to http and use env at last + string UseProxy = _config->Find("Acquire::https::Proxy::" + ServerName.Host, + _config->Find("Acquire::http::Proxy::" + ServerName.Host).c_str()); + + if (UseProxy.empty() == true) + UseProxy = _config->Find("Acquire::https::Proxy", _config->Find("Acquire::http::Proxy").c_str()); + + // User want to use NO proxy, so nothing to setup + if (UseProxy == "DIRECT") + return; + + if (UseProxy.empty() == false) { - if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) - Proxy = ""; + // Parse no_proxy, a comma (,) separated list of domains we don't want to use + // a proxy for so we stop right here if it is in the list + if (getenv("no_proxy") != 0 && CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) + return; + } else { + const char* result = getenv("http_proxy"); + UseProxy = result == NULL ? "" : result; } - + // Determine what host and port to use based on the proxy settings - int Port = 0; - string Host; - if (Proxy.empty() == true || Proxy.Host.empty() == true) - { - } - else + if (UseProxy.empty() == false) { - if (Proxy.Port != 0) + Proxy = UseProxy; + if (Proxy.Port != 1) curl_easy_setopt(curl, CURLOPT_PROXYPORT, Proxy.Port); curl_easy_setopt(curl, CURLOPT_PROXY, Proxy.Host.c_str()); } -} - - +} /*}}}*/ // HttpsMethod::Fetch - Fetch an item /*{{{*/ // --------------------------------------------------------------------- /* This adds an item to the pipeline. We keep the pipeline at a fixed depth. */ bool HttpsMethod::Fetch(FetchItem *Itm) { - stringstream ss; struct stat SBuf; struct curl_slist *headers=NULL; char curl_errorstr[CURL_ERROR_SIZE]; + long curl_responsecode; + URI Uri = Itm->Uri; + string remotehost = Uri.Host; // TODO: - // - http::Timeout // - http::Pipeline-Depth // - error checking/reporting // - more debug options? (CURLOPT_DEBUGFUNCTION?) + curl_easy_reset(curl); SetupProxy(); + maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc")); + // callbacks - curl_easy_setopt(curl, CURLOPT_URL, Itm->Uri.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, static_cast(Uri).c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); curl_easy_setopt(curl, CURLOPT_FAILONERROR, true); - - // FIXME: https: offer various options of verification - bool peer_verify = _config->FindB("Acquire::https::Verify-Peer", false); + curl_easy_setopt(curl, CURLOPT_FILETIME, true); + + // SSL parameters are set by default to the common (non mirror-specific) value + // if available (or a default one) and gets overload by mirror-specific ones. + + // File containing the list of trusted CA. + string cainfo = _config->Find("Acquire::https::CaInfo",""); + string knob = "Acquire::https::"+remotehost+"::CaInfo"; + cainfo = _config->Find(knob.c_str(),cainfo.c_str()); + if(cainfo.empty() == false) + curl_easy_setopt(curl, CURLOPT_CAINFO,cainfo.c_str()); + + // Check server certificate against previous CA list ... + bool peer_verify = _config->FindB("Acquire::https::Verify-Peer",true); + knob = "Acquire::https::" + remotehost + "::Verify-Peer"; + peer_verify = _config->FindB(knob.c_str(), peer_verify); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, peer_verify); - // sslcert file + // ... and hostname against cert CN or subjectAltName + bool verify = _config->FindB("Acquire::https::Verify-Host",true); + knob = "Acquire::https::"+remotehost+"::Verify-Host"; + verify = _config->FindB(knob.c_str(),verify); + int const default_verify = (verify == true) ? 2 : 0; + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, default_verify); + + // Also enforce issuer of server certificate using its cert + string issuercert = _config->Find("Acquire::https::IssuerCert",""); + knob = "Acquire::https::"+remotehost+"::IssuerCert"; + issuercert = _config->Find(knob.c_str(),issuercert.c_str()); + if(issuercert.empty() == false) + curl_easy_setopt(curl, CURLOPT_ISSUERCERT,issuercert.c_str()); + + // For client authentication, certificate file ... string pem = _config->Find("Acquire::https::SslCert",""); - if(pem != "") + knob = "Acquire::https::"+remotehost+"::SslCert"; + pem = _config->Find(knob.c_str(),pem.c_str()); + if(pem.empty() == false) curl_easy_setopt(curl, CURLOPT_SSLCERT, pem.c_str()); - - // CA-Dir - string certdir = _config->Find("Acquire::https::CaPath",""); - if(certdir != "") - curl_easy_setopt(curl, CURLOPT_CAPATH, certdir.c_str()); - - // Server-verify - int verify = _config->FindI("Acquire::https::Verify-Host",2); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, verify); + + // ... and associated key. + string key = _config->Find("Acquire::https::SslKey",""); + knob = "Acquire::https::"+remotehost+"::SslKey"; + key = _config->Find(knob.c_str(),key.c_str()); + if(key.empty() == false) + curl_easy_setopt(curl, CURLOPT_SSLKEY, key.c_str()); + + // Allow forcing SSL version to SSLv3 or TLSv1 (SSLv2 is not + // supported by GnuTLS). + long final_version = CURL_SSLVERSION_DEFAULT; + string sslversion = _config->Find("Acquire::https::SslForceVersion",""); + knob = "Acquire::https::"+remotehost+"::SslForceVersion"; + sslversion = _config->Find(knob.c_str(),sslversion.c_str()); + if(sslversion == "TLSv1") + final_version = CURL_SSLVERSION_TLSv1; + else if(sslversion == "SSLv3") + final_version = CURL_SSLVERSION_SSLv3; + curl_easy_setopt(curl, CURLOPT_SSLVERSION, final_version); + + // CRL file + string crlfile = _config->Find("Acquire::https::CrlFile",""); + knob = "Acquire::https::"+remotehost+"::CrlFile"; + crlfile = _config->Find(knob.c_str(),crlfile.c_str()); + if(crlfile.empty() == false) + curl_easy_setopt(curl, CURLOPT_CRLFILE, crlfile.c_str()); // cache-control - if(_config->FindB("Acquire::http::No-Cache",false) == false) + if(_config->FindB("Acquire::https::No-Cache", + _config->FindB("Acquire::http::No-Cache",false)) == false) { // cache enabled - if (_config->FindB("Acquire::http::No-Store",false) == true) + if (_config->FindB("Acquire::https::No-Store", + _config->FindB("Acquire::http::No-Store",false)) == true) headers = curl_slist_append(headers,"Cache-Control: no-store"); - ioprintf(ss, "Cache-Control: max-age=%u", _config->FindI("Acquire::http::Max-Age",0)); + stringstream ss; + ioprintf(ss, "Cache-Control: max-age=%u", _config->FindI("Acquire::https::Max-Age", + _config->FindI("Acquire::http::Max-Age",0))); headers = curl_slist_append(headers, ss.str().c_str()); } else { // cache disabled by user @@ -159,17 +209,31 @@ bool HttpsMethod::Fetch(FetchItem *Itm) } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - // set time values - curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); - curl_easy_setopt(curl, CURLOPT_TIMEVALUE, Itm->LastModified); - // speed limit - int dlLimit = _config->FindI("Acquire::http::Dl-Limit",0)*1024; + int const dlLimit = _config->FindI("Acquire::https::Dl-Limit", + _config->FindI("Acquire::http::Dl-Limit",0))*1024; if (dlLimit > 0) curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, dlLimit); // set header - curl_easy_setopt(curl, CURLOPT_USERAGENT,"Debian APT-CURL/1.0 ("VERSION")"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, + _config->Find("Acquire::https::User-Agent", + _config->Find("Acquire::http::User-Agent", + "Debian APT-CURL/1.0 (" PACKAGE_VERSION ")").c_str()).c_str()); + + // set timeout + int const timeout = _config->FindI("Acquire::https::Timeout", + _config->FindI("Acquire::http::Timeout",120)); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); + //set really low lowspeed timeout (see #497983) + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, DL_MIN_SPEED); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout); + + // set redirect options and default to 10 redirects + bool const AllowRedirect = _config->FindB("Acquire::https::AllowRedirect", + _config->FindB("Acquire::http::AllowRedirect",true)); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, AllowRedirect); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10); // debug if(_config->FindB("Debug::Acquire::https", false)) @@ -178,57 +242,98 @@ bool HttpsMethod::Fetch(FetchItem *Itm) // error handling curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr); - // In this case we send an if-range query with a range header - if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) - curl_easy_setopt(curl, CURLOPT_RESUME_FROM, (long)SBuf.st_size); + // If we ask for uncompressed files servers might respond with content- + // negotation which lets us end up with compressed files we do not support, + // see 657029, 657560 and co, so if we have no extension on the request + // ask for text only. As a sidenote: If there is nothing to negotate servers + // seem to be nice and ignore it. + if (_config->FindB("Acquire::https::SendAccept", _config->FindB("Acquire::http::SendAccept", true)) == true) + { + size_t const filepos = Itm->Uri.find_last_of('/'); + string const file = Itm->Uri.substr(filepos + 1); + if (flExtension(file) == file) + headers = curl_slist_append(headers, "Accept: text/*"); + } + + // if we have the file send an if-range query with a range header + if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) + { + char Buf[1000]; + sprintf(Buf, "Range: bytes=%li-", (long) SBuf.st_size - 1); + headers = curl_slist_append(headers, Buf); + sprintf(Buf, "If-Range: %s", TimeRFC1123(SBuf.st_mtime).c_str()); + headers = curl_slist_append(headers, Buf); + } + else if(Itm->LastModified > 0) + { + curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + curl_easy_setopt(curl, CURLOPT_TIMEVALUE, Itm->LastModified); + } // go for it - if the file exists, append on it File = new FileFd(Itm->DestFile, FileFd::WriteAny); - File->Seek(File->Size()); + if (File->Size() > 0) + File->Seek(File->Size() - 1); // keep apt updated Res.Filename = Itm->DestFile; // get it! CURLcode success = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &curl_responsecode); + long curl_servdate; + curl_easy_getinfo(curl, CURLINFO_FILETIME, &curl_servdate); + + File->Close(); // cleanup - if(success != 0) { - _error->Error(curl_errorstr); + if(success != 0) + { + _error->Error("%s", curl_errorstr); + // unlink, no need keep 401/404 page content in partial/ + unlink(File->Name().c_str()); Fail(); return true; } - if (Res.Size == 0) - Res.Size = File->Size(); + // Timestamp + struct utimbuf UBuf; + if (curl_servdate != -1) { + UBuf.actime = curl_servdate; + UBuf.modtime = curl_servdate; + utime(File->Name().c_str(),&UBuf); + } // check the downloaded result struct stat Buf; if (stat(File->Name().c_str(),&Buf) == 0) { - Res.Size = Buf.st_size; Res.Filename = File->Name(); Res.LastModified = Buf.st_mtime; Res.IMSHit = false; - if (Itm->LastModified != 0 && Buf.st_mtime >= Itm->LastModified) + if (curl_responsecode == 304) { + unlink(File->Name().c_str()); Res.IMSHit = true; Res.LastModified = Itm->LastModified; + Res.Size = 0; + URIDone(Res); + return true; } + Res.Size = Buf.st_size; } // take hashes Hashes Hash; FileFd Fd(Res.Filename, FileFd::ReadOnly); - Hash.AddFD(Fd.Fd(), Fd.Size()); + Hash.AddFD(Fd); Res.TakeHashes(Hash); // keep apt updated URIDone(Res); // cleanup - File->Close(); Res.Size = 0; delete File; curl_slist_free_all(headers);