diff options
-rw-r--r-- | .clang-format | 9 | ||||
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 16 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | doc/apt.conf.5.xml | 9 | ||||
-rw-r--r-- | doc/examples/configure-index | 2 | ||||
-rwxr-xr-x | git-clang-format.sh | 2 | ||||
-rw-r--r-- | methods/CMakeLists.txt | 26 | ||||
-rw-r--r-- | methods/connect.cc | 438 | ||||
-rw-r--r-- | methods/connect.h | 35 | ||||
-rw-r--r-- | methods/ftp.cc | 27 | ||||
-rw-r--r-- | methods/ftp.h | 7 | ||||
-rw-r--r-- | methods/http.cc | 260 | ||||
-rw-r--r-- | methods/http.h | 10 | ||||
-rw-r--r-- | methods/http_main.cc | 2 | ||||
-rw-r--r-- | po/CMakeLists.txt | 8 | ||||
-rw-r--r-- | shippable.yml | 2 | ||||
-rwxr-xr-x | test/integration/test-apt-https-no-redirect | 10 | ||||
-rwxr-xr-x | test/integration/test-apt-update-failure-propagation | 6 |
19 files changed, 601 insertions, 272 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..00edbfc92 --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +Language: Cpp +# BasedOnStyle + +TabWidth: 8 +UseTab: Always +IndentWidth: 3 +ColumnLimit: 0 +BreakBeforeBraces: Allman +AccessModifierOffset: 0 diff --git a/.travis.yml b/.travis.yml index 023107a68..8aa8dceb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ sudo: required dist: trusty env: - TEST_SUITE=user CMAKE_FLAGS= + - TEST_SUITE=user CMAKE_FLAGS="-DWITH_DOC=OFF -DWITH_CURL=OFF" - TEST_SUITE=root CMAKE_FLAGS=-DWITH_DOC=OFF before_install: - sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ wily main universe' -y @@ -14,7 +15,7 @@ before_install: - sudo apt-get update -qq install: - sudo apt-get -qq -y -t wily install gettext liblz4-dev python3-apt - - sudo apt-get -qq -y -t xenial install cmake ninja-build + - sudo apt-get -qq -y -t xenial install cmake ninja-build libgnutls28-dev libcurl4-gnutls-dev - sudo ./prepare-release travis-ci before_script: - ( mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Coverage -G Ninja $CMAKE_FLAGS .. ) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83af9bd5a..a0690923c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,9 +77,19 @@ endif() # apt-transport-https dependencies -find_package(CURL REQUIRED) -if (CURL_FOUND) - set(HAVE_CURL 1) +option(WITH_CURL "Build curl-based methods" ON) +if (WITH_CURL) + find_package(CURL REQUIRED) + if (CURL_FOUND) + set(HAVE_CURL 1) + endif() +else() + set(HAVE_CURL 0) +endif() + +find_package(GnuTLS REQUIRED) +if (GNUTLS_FOUND) + set(HAVE_GNUTLS 1) endif() # (De)Compressor libraries diff --git a/debian/control b/debian/control index 96bbef348..04c44eb93 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: cmake (>= 3.4), dpkg-dev (>= 1.17.14), gettext (>= 0.12), libbz2-dev, + libgnutls28-dev (>= 3.4.6), libcurl4-gnutls-dev (>= 7.19.4~), libdb-dev, googletest <!nocheck> | libgtest-dev <!nocheck>, diff --git a/doc/apt.conf.5.xml b/doc/apt.conf.5.xml index 260c66c46..54ed78c95 100644 --- a/doc/apt.conf.5.xml +++ b/doc/apt.conf.5.xml @@ -357,6 +357,15 @@ APT::Compressor::rev { </para></listitem> </varlistentry> + <varlistentry><term><option>AllowTLS</option></term> + <listitem><para> + Allow use of the internal TLS support in the http method. If set to false, + this completely disables support for TLS in apt's own methods (excluding + the curl-based https method). No TLS-related functions will be called + anymore. + </para></listitem> + </varlistentry> + <varlistentry><term><option>PDiffs</option></term> <listitem><para>Try to download deltas called <literal>PDiffs</literal> for indexes (like <filename>Packages</filename> files) instead of diff --git a/doc/examples/configure-index b/doc/examples/configure-index index aada67bf5..a48d4cb99 100644 --- a/doc/examples/configure-index +++ b/doc/examples/configure-index @@ -206,6 +206,8 @@ Acquire Source-Symlinks "<BOOL>"; ForceHash "<STRING>"; // hashmethod used for expected hash: sha256, sha1 or md5sum + AllowTLS "<BOOL>"; // whether support for tls is enabled + PDiffs "<BOOL>"; // try to get the IndexFile diffs PDiffs::FileLimit "<INT>"; // don't use diffs if we would need more than 4 diffs PDiffs::SizeLimit "<INT>"; // don't use diffs if size of all patches excess X% of the size of the original file diff --git a/git-clang-format.sh b/git-clang-format.sh new file mode 100755 index 000000000..7cbb5ccd7 --- /dev/null +++ b/git-clang-format.sh @@ -0,0 +1,2 @@ +#!/bin/sh +git clang-format-3.8 --diff "$@" | sed "s#+/\*\}\}\}\*/#+ /*}}}*/#" | patch -p1 diff --git a/methods/CMakeLists.txt b/methods/CMakeLists.txt index a74c2ce07..9f01ec506 100644 --- a/methods/CMakeLists.txt +++ b/methods/CMakeLists.txt @@ -6,13 +6,18 @@ add_executable(gpgv gpgv.cc) add_executable(cdrom cdrom.cc) add_executable(http http.cc http_main.cc rfc2553emu.cc connect.cc basehttp.cc) add_executable(mirror mirror.cc http.cc rfc2553emu.cc connect.cc basehttp.cc) -add_executable(https https.cc basehttp.cc) +if (HAVE_CURL) + add_executable(https https.cc basehttp.cc) +endif() add_executable(ftp ftp.cc rfc2553emu.cc connect.cc) add_executable(rred rred.cc) add_executable(rsh rsh.cc) -# Add target-specific header directories +target_compile_definitions(http PRIVATE ${GNUTLS_DEFINITIONS}) +target_include_directories(http PRIVATE ${GNUTLS_INCLUDE_DIR}) +if (HAVE_CURL) target_include_directories(https PRIVATE ${CURL_INCLUDE_DIRS}) +endif() # Link the executables against the libraries target_link_libraries(file apt-pkg) @@ -20,16 +25,23 @@ target_link_libraries(copy apt-pkg) target_link_libraries(store apt-pkg) target_link_libraries(gpgv apt-pkg) target_link_libraries(cdrom apt-pkg) -target_link_libraries(http apt-pkg) -target_link_libraries(mirror apt-pkg ${RESOLV_LIBRARIES}) -target_link_libraries(https apt-pkg ${CURL_LIBRARIES}) -target_link_libraries(ftp apt-pkg) +target_link_libraries(http apt-pkg ${GNUTLS_LIBRARIES}) +target_link_libraries(mirror apt-pkg ${RESOLV_LIBRARIES} ${GNUTLS_LIBRARIES}) +if (HAVE_CURL) + target_link_libraries(https apt-pkg ${CURL_LIBRARIES}) +endif() +target_link_libraries(ftp apt-pkg ${GNUTLS_LIBRARIES}) target_link_libraries(rred apt-pkg) target_link_libraries(rsh apt-pkg) # Install the library -install(TARGETS file copy store gpgv cdrom http https ftp rred rsh mirror +install(TARGETS file copy store gpgv cdrom http ftp rred rsh mirror RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/methods) add_slaves(${CMAKE_INSTALL_LIBEXECDIR}/apt/methods store gzip lzma bzip2 xz) add_slaves(${CMAKE_INSTALL_LIBEXECDIR}/apt/methods rsh ssh) +if (HAVE_CURL) + install(TARGETS https RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/methods) +else() + add_slaves(${CMAKE_INSTALL_LIBEXECDIR}/apt/methods http https) +endif() diff --git a/methods/connect.cc b/methods/connect.cc index f6fb14769..0103b5873 100644 --- a/methods/connect.cc +++ b/methods/connect.cc @@ -20,6 +20,9 @@ #include <apt-pkg/configuration.h> #include <apt-pkg/srvrec.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + #include <stdio.h> #include <errno.h> #include <unistd.h> @@ -34,6 +37,7 @@ #include <arpa/inet.h> #include <netdb.h> +#include "aptmethod.h" #include "connect.h" #include "rfc2553emu.h" #include <apti18n.h> @@ -76,16 +80,46 @@ static bool ConnectionAllowed(char const * const Service, std::string const &Hos return true; } /*}}}*/ + +// File Descriptor based Fd /*{{{*/ +struct FdFd : public MethodFd +{ + int fd = -1; + int Fd() APT_OVERRIDE { return fd; } + ssize_t Read(void *buf, size_t count) APT_OVERRIDE { return ::read(fd, buf, count); } + ssize_t Write(void *buf, size_t count) APT_OVERRIDE { return ::write(fd, buf, count); } + int Close() APT_OVERRIDE + { + int result = 0; + if (fd != -1) + result = ::close(fd); + fd = -1; + return result; + } +}; + +bool MethodFd::HasPending() +{ + return false; +} +std::unique_ptr<MethodFd> MethodFd::FromFd(int iFd) +{ + FdFd *fd = new FdFd(); + fd->fd = iFd; + return std::unique_ptr<MethodFd>(fd); +} + /*}}}*/ // DoConnect - Attempt a connect operation /*{{{*/ // --------------------------------------------------------------------- /* This helper function attempts a connection to a single address. */ -static bool DoConnect(struct addrinfo *Addr,std::string const &Host, - unsigned long TimeOut,int &Fd,pkgAcqMethod *Owner) +static bool DoConnect(struct addrinfo *Addr, std::string const &Host, + unsigned long TimeOut, std::unique_ptr<MethodFd> &Fd, aptMethod *Owner) { // Show a status indicator char Name[NI_MAXHOST]; char Service[NI_MAXSERV]; - + Fd.reset(new FdFd()); + Name[0] = 0; Service[0] = 0; getnameinfo(Addr->ai_addr,Addr->ai_addrlen, @@ -107,20 +141,21 @@ static bool DoConnect(struct addrinfo *Addr,std::string const &Host, } // Get a socket - if ((Fd = socket(Addr->ai_family,Addr->ai_socktype, - Addr->ai_protocol)) < 0) + if ((static_cast<FdFd *>(Fd.get())->fd = socket(Addr->ai_family, Addr->ai_socktype, + Addr->ai_protocol)) < 0) return _error->Errno("socket",_("Could not create a socket for %s (f=%u t=%u p=%u)"), Name,Addr->ai_family,Addr->ai_socktype,Addr->ai_protocol); - - SetNonBlock(Fd,true); - if (connect(Fd,Addr->ai_addr,Addr->ai_addrlen) < 0 && + + SetNonBlock(Fd->Fd(), true); + if (connect(Fd->Fd(), Addr->ai_addr, Addr->ai_addrlen) < 0 && errno != EINPROGRESS) return _error->Errno("connect",_("Cannot initiate the connection " "to %s:%s (%s)."),Host.c_str(),Service,Name); /* This implements a timeout for connect by opening the connection nonblocking */ - if (WaitFd(Fd,true,TimeOut) == false) { + if (WaitFd(Fd->Fd(), true, TimeOut) == false) + { bad_addr.insert(bad_addr.begin(), std::string(Name)); Owner->SetFailReason("Timeout"); return _error->Error(_("Could not connect to %s:%s (%s), " @@ -130,7 +165,7 @@ static bool DoConnect(struct addrinfo *Addr,std::string const &Host, // Check the socket for an error condition unsigned int Err; unsigned int Len = sizeof(Err); - if (getsockopt(Fd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0) + if (getsockopt(Fd->Fd(), SOL_SOCKET, SO_ERROR, &Err, &Len) != 0) return _error->Errno("getsockopt",_("Failed")); if (Err != 0) @@ -150,8 +185,8 @@ static bool DoConnect(struct addrinfo *Addr,std::string const &Host, /*}}}*/ // Connect to a given Hostname /*{{{*/ static bool ConnectToHostname(std::string const &Host, int const Port, - const char * const Service, int DefPort, int &Fd, - unsigned long const TimeOut, pkgAcqMethod * const Owner) + const char *const Service, int DefPort, std::unique_ptr<MethodFd> &Fd, + unsigned long const TimeOut, aptMethod *const Owner) { if (ConnectionAllowed(Service, Host) == false) return false; @@ -254,10 +289,9 @@ static bool ConnectToHostname(std::string const &Host, int const Port, { LastUsed = CurHost; return true; - } - close(Fd); - Fd = -1; - + } + Fd->Close(); + // Ignore UNIX domain sockets do { @@ -286,9 +320,9 @@ static bool ConnectToHostname(std::string const &Host, int const Port, // Connect - Connect to a server /*{{{*/ // --------------------------------------------------------------------- /* Performs a connection to the server (including SRV record lookup) */ -bool Connect(std::string Host,int Port,const char *Service, - int DefPort,int &Fd, - unsigned long TimeOut,pkgAcqMethod *Owner) +bool Connect(std::string Host, int Port, const char *Service, + int DefPort, std::unique_ptr<MethodFd> &Fd, + unsigned long TimeOut, aptMethod *Owner) { if (_error->PendingError() == true) return false; @@ -340,3 +374,369 @@ bool Connect(std::string Host,int Port,const char *Service, _error->MergeWithStack(); return ret; } + /*}}}*/ +// UnwrapSocks - Handle SOCKS setup /*{{{*/ +// --------------------------------------------------------------------- +/* This does socks magic */ +static bool TalkToSocksProxy(int const ServerFd, std::string const &Proxy, + char const *const type, bool const ReadWrite, uint8_t *const ToFrom, + unsigned int const Size, unsigned int const Timeout) +{ + if (WaitFd(ServerFd, ReadWrite, Timeout) == false) + return _error->Error("Waiting for the SOCKS proxy %s to %s timed out", URI::SiteOnly(Proxy).c_str(), type); + if (ReadWrite == false) + { + if (FileFd::Read(ServerFd, ToFrom, Size) == false) + return _error->Error("Reading the %s from SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str()); + } + else + { + if (FileFd::Write(ServerFd, ToFrom, Size) == false) + return _error->Error("Writing the %s to SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str()); + } + return true; +} + +bool UnwrapSocks(std::string Host, int Port, URI Proxy, std::unique_ptr<MethodFd> &Fd, + unsigned long Timeout, aptMethod *Owner) +{ + /* We implement a very basic SOCKS5 client here complying mostly to RFC1928 expect + * for not offering GSSAPI auth which is a must (we only do no or user/pass auth). + * We also expect the SOCKS5 server to do hostname lookup (aka socks5h) */ + std::string const ProxyInfo = URI::SiteOnly(Proxy); + Owner->Status(_("Connecting to %s (%s)"), "SOCKS5h proxy", ProxyInfo.c_str()); +#define APT_WriteOrFail(TYPE, DATA, LENGTH) \ + if (TalkToSocksProxy(Fd->Fd(), ProxyInfo, TYPE, true, DATA, LENGTH, Timeout) == false) \ + return false +#define APT_ReadOrFail(TYPE, DATA, LENGTH) \ + if (TalkToSocksProxy(Fd->Fd(), ProxyInfo, TYPE, false, DATA, LENGTH, Timeout) == false) \ + return false + if (Host.length() > 255) + return _error->Error("Can't use SOCKS5h as hostname %s is too long!", Host.c_str()); + if (Proxy.User.length() > 255 || Proxy.Password.length() > 255) + return _error->Error("Can't use user&pass auth as they are too long (%lu and %lu) for the SOCKS5!", Proxy.User.length(), Proxy.Password.length()); + if (Proxy.User.empty()) + { + uint8_t greeting[] = {0x05, 0x01, 0x00}; + APT_WriteOrFail("greet-1", greeting, sizeof(greeting)); + } + else + { + uint8_t greeting[] = {0x05, 0x02, 0x00, 0x02}; + APT_WriteOrFail("greet-2", greeting, sizeof(greeting)); + } + uint8_t greeting[2]; + APT_ReadOrFail("greet back", greeting, sizeof(greeting)); + if (greeting[0] != 0x05) + return _error->Error("SOCKS proxy %s greets back with wrong version: %d", ProxyInfo.c_str(), greeting[0]); + if (greeting[1] == 0x00) + ; // no auth has no method-dependent sub-negotiations + else if (greeting[1] == 0x02) + { + if (Proxy.User.empty()) + return _error->Error("SOCKS proxy %s negotiated user&pass auth, but we had not offered it!", ProxyInfo.c_str()); + // user&pass auth sub-negotiations are defined by RFC1929 + std::vector<uint8_t> auth = {{0x01, static_cast<uint8_t>(Proxy.User.length())}}; + std::copy(Proxy.User.begin(), Proxy.User.end(), std::back_inserter(auth)); + auth.push_back(static_cast<uint8_t>(Proxy.Password.length())); + std::copy(Proxy.Password.begin(), Proxy.Password.end(), std::back_inserter(auth)); + APT_WriteOrFail("user&pass auth", auth.data(), auth.size()); + uint8_t authstatus[2]; + APT_ReadOrFail("auth report", authstatus, sizeof(authstatus)); + if (authstatus[0] != 0x01) + return _error->Error("SOCKS proxy %s auth status response with wrong version: %d", ProxyInfo.c_str(), authstatus[0]); + if (authstatus[1] != 0x00) + return _error->Error("SOCKS proxy %s reported authorization failure: username or password incorrect? (%d)", ProxyInfo.c_str(), authstatus[1]); + } + else + return _error->Error("SOCKS proxy %s greets back having not found a common authorization method: %d", ProxyInfo.c_str(), greeting[1]); + union { + uint16_t *i; + uint8_t *b; + } portu; + uint16_t port = htons(static_cast<uint16_t>(Port)); + portu.i = &port; + std::vector<uint8_t> request = {{0x05, 0x01, 0x00, 0x03, static_cast<uint8_t>(Host.length())}}; + std::copy(Host.begin(), Host.end(), std::back_inserter(request)); + request.push_back(portu.b[0]); + request.push_back(portu.b[1]); + APT_WriteOrFail("request", request.data(), request.size()); + uint8_t response[4]; + APT_ReadOrFail("first part of response", response, sizeof(response)); + if (response[0] != 0x05) + return _error->Error("SOCKS proxy %s response with wrong version: %d", ProxyInfo.c_str(), response[0]); + if (response[2] != 0x00) + return _error->Error("SOCKS proxy %s has unexpected non-zero reserved field value: %d", ProxyInfo.c_str(), response[2]); + std::string bindaddr; + if (response[3] == 0x01) // IPv4 address + { + uint8_t ip4port[6]; + APT_ReadOrFail("IPv4+Port of response", ip4port, sizeof(ip4port)); + portu.b[0] = ip4port[4]; + portu.b[1] = ip4port[5]; + port = ntohs(*portu.i); + strprintf(bindaddr, "%d.%d.%d.%d:%d", ip4port[0], ip4port[1], ip4port[2], ip4port[3], port); + } + else if (response[3] == 0x03) // hostname + { + uint8_t namelength; + APT_ReadOrFail("hostname length of response", &namelength, 1); + uint8_t hostname[namelength + 2]; + APT_ReadOrFail("hostname of response", hostname, sizeof(hostname)); + portu.b[0] = hostname[namelength]; + portu.b[1] = hostname[namelength + 1]; + port = ntohs(*portu.i); + hostname[namelength] = '\0'; + strprintf(bindaddr, "%s:%d", hostname, port); + } + else if (response[3] == 0x04) // IPv6 address + { + uint8_t ip6port[18]; + APT_ReadOrFail("IPv6+port of response", ip6port, sizeof(ip6port)); + portu.b[0] = ip6port[16]; + portu.b[1] = ip6port[17]; + port = ntohs(*portu.i); + strprintf(bindaddr, "[%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X]:%d", + ip6port[0], ip6port[1], ip6port[2], ip6port[3], ip6port[4], ip6port[5], ip6port[6], ip6port[7], + ip6port[8], ip6port[9], ip6port[10], ip6port[11], ip6port[12], ip6port[13], ip6port[14], ip6port[15], + port); + } + else + return _error->Error("SOCKS proxy %s destination address is of unknown type: %d", + ProxyInfo.c_str(), response[3]); + if (response[1] != 0x00) + { + char const *errstr = nullptr; + auto errcode = response[1]; + // Tor error reporting can be a bit arcane, lets try to detect & fix it up + if (bindaddr == "0.0.0.0:0") + { + auto const lastdot = Host.rfind('.'); + if (lastdot == std::string::npos || Host.substr(lastdot) != ".onion") + ; + else if (errcode == 0x01) + { + auto const prevdot = Host.rfind('.', lastdot - 1); + if (lastdot == 16 && prevdot == std::string::npos) + ; // valid .onion address + else if (prevdot != std::string::npos && (lastdot - prevdot) == 17) + ; // valid .onion address with subdomain(s) + else + { + errstr = "Invalid hostname: onion service name must be 16 characters long"; + Owner->SetFailReason("SOCKS"); + } + } + // in all likelihood the service is either down or the address has + // a typo and so "Host unreachable" is the better understood error + // compared to the technically correct "TLL expired". + else if (errcode == 0x06) + errcode = 0x04; + } + if (errstr == nullptr) + { + switch (errcode) + { + case 0x01: + errstr = "general SOCKS server failure"; + Owner->SetFailReason("SOCKS"); + break; + case 0x02: + errstr = "connection not allowed by ruleset"; + Owner->SetFailReason("SOCKS"); + break; + case 0x03: + errstr = "Network unreachable"; + Owner->SetFailReason("ConnectionTimedOut"); + break; + case 0x04: + errstr = "Host unreachable"; + Owner->SetFailReason("ConnectionTimedOut"); + break; + case 0x05: + errstr = "Connection refused"; + Owner->SetFailReason("ConnectionRefused"); + break; + case 0x06: + errstr = "TTL expired"; + Owner->SetFailReason("Timeout"); + break; + case 0x07: + errstr = "Command not supported"; + Owner->SetFailReason("SOCKS"); + break; + case 0x08: + errstr = "Address type not supported"; + Owner->SetFailReason("SOCKS"); + break; + default: + errstr = "Unknown error"; + Owner->SetFailReason("SOCKS"); + break; + } + } + return _error->Error("SOCKS proxy %s could not connect to %s (%s) due to: %s (%d)", + ProxyInfo.c_str(), Host.c_str(), bindaddr.c_str(), errstr, response[1]); + } + else if (Owner->DebugEnabled()) + ioprintf(std::clog, "http: SOCKS proxy %s connection established to %s (%s)\n", + ProxyInfo.c_str(), Host.c_str(), bindaddr.c_str()); + + if (WaitFd(Fd->Fd(), true, Timeout) == false) + return _error->Error("SOCKS proxy %s reported connection to %s (%s), but timed out", + ProxyInfo.c_str(), Host.c_str(), bindaddr.c_str()); +#undef APT_ReadOrFail +#undef APT_WriteOrFail + + return true; +} + /*}}}*/ +// UnwrapTLS - Handle TLS connections /*{{{*/ +// --------------------------------------------------------------------- +/* Performs a TLS handshake on the socket */ +struct TlsFd : public MethodFd +{ + std::unique_ptr<MethodFd> UnderlyingFd; + gnutls_session_t session; + gnutls_certificate_credentials_t credentials; + std::string hostname; + + int Fd() APT_OVERRIDE { return UnderlyingFd->Fd(); } + + ssize_t Read(void *buf, size_t count) APT_OVERRIDE + { + return HandleError(gnutls_record_recv(session, buf, count)); + } + ssize_t Write(void *buf, size_t count) APT_OVERRIDE + { + return HandleError(gnutls_record_send(session, buf, count)); + } + + template <typename T> + T HandleError(T err) + { + if (err < 0 && gnutls_error_is_fatal(err)) + errno = EIO; + else if (err < 0) + errno = EAGAIN; + else + errno = 0; + return err; + } + + int Close() APT_OVERRIDE + { + if (HandleError(gnutls_bye(session, GNUTLS_SHUT_RDWR)) < 0) + return -1; + return UnderlyingFd->Close(); + } + + bool HasPending() APT_OVERRIDE + { + return gnutls_record_check_pending(session) > 0; + } +}; + +bool UnwrapTLS(std::string Host, std::unique_ptr<MethodFd> &Fd, + unsigned long Timeout, aptMethod *Owner) +{ + if (_config->FindB("Acquire::AllowTLS", true) == false) + return _error->Error("TLS support has been disabled: Acquire::AllowTLS is false."); + + int err; + TlsFd *tlsFd = new TlsFd(); + + tlsFd->hostname = Host; + tlsFd->UnderlyingFd = MethodFd::FromFd(-1); // For now + + gnutls_init(&tlsFd->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK); + gnutls_transport_set_int(tlsFd->session, dynamic_cast<FdFd *>(Fd.get())->fd); + gnutls_certificate_allocate_credentials(&tlsFd->credentials); + + // Credential setup + if ((err = gnutls_certificate_set_x509_system_trust(tlsFd->credentials)) <= 0) + return _error->Error("Could not load TLS certificates: %s", gnutls_strerror(err)); + + std::string fileinfo = Owner->ConfigFind("CaInfo", ""); + if (!fileinfo.empty()) + { + gnutls_certificate_set_verify_flags(tlsFd->credentials, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + err = gnutls_certificate_set_x509_trust_file(tlsFd->credentials, fileinfo.c_str(), GNUTLS_X509_FMT_PEM); + if (err < 0) + return _error->Error("Could not load CaInfo certificate: %s", gnutls_strerror(err)); + } + + // TODO: IssuerCert AKA CURLOPT_ISSUERCERT + // TODO: Emulate SslForceVersion AKA CURLOPT_SSLVERSION? + + // For client authentication, certificate file ... + std::string const cert = Owner->ConfigFind("SslCert", ""); + std::string const key = Owner->ConfigFind("SslKey", ""); + if (cert.empty() == false) + { + if ((err = gnutls_certificate_set_x509_key_file( + tlsFd->credentials, + cert.c_str(), + key.empty() ? cert.c_str() : key.c_str(), + GNUTLS_X509_FMT_PEM)) < 0) + { + return _error->Error("Could not load client certificate or key: %s", gnutls_strerror(err)); + } + } + + // CRL file + std::string const crlfile = Owner->ConfigFind("CrlFile", ""); + if (crlfile.empty() == false) + { + if ((err = gnutls_certificate_set_x509_crl_file(tlsFd->credentials, + crlfile.c_str(), + GNUTLS_X509_FMT_PEM)) < 0) + return _error->Error("Could not load CrlFile: %s", gnutls_strerror(err)); + } + + if ((err = gnutls_credentials_set(tlsFd->session, GNUTLS_CRD_CERTIFICATE, tlsFd->credentials)) < 0) + return _error->Error("Could not set CaInfo certificate: %s", gnutls_strerror(err)); + + if ((err = gnutls_set_default_priority(tlsFd->session)) < 0) + return _error->Error("Could not set algorithm preferences: %s", gnutls_strerror(err)); + + if (Owner->ConfigFindB("Verify-Peer", true) || Owner->ConfigFindB("Verify-Host", true)) + { + gnutls_session_set_verify_cert(tlsFd->session, tlsFd->hostname.c_str(), 0); + } + if ((err = gnutls_server_name_set(tlsFd->session, GNUTLS_NAME_DNS, tlsFd->hostname.c_str(), tlsFd->hostname.length())) < 0) + return _error->Error("Could not set SNI name: %s", gnutls_strerror(err)); + + // Do the handshake. Our socket is non-blocking, so we need to call WaitFd() + // accordingly. + do + { + err = gnutls_handshake(tlsFd->session); + if ((err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN) && + WaitFd(Fd->Fd(), gnutls_record_get_direction(tlsFd->session) == 1, Timeout) == false) + return _error->Errno("select", "Could not wait for server fd"); + } while (err < 0 && gnutls_error_is_fatal(err) == 0); + + if (err < 0) + { + // Print reason why validation failed. + if (err == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) + { + gnutls_datum_t txt; + auto type = gnutls_certificate_type_get(tlsFd->session); + auto status = gnutls_session_get_verify_cert_status(tlsFd->session); + if (gnutls_certificate_verification_status_print(status, + type, &txt, 0) == 0) + { + _error->Error("Certificate verification failed: %s", txt.data); + } + gnutls_free(txt.data); + } + return _error->Error("Could not handshake: %s", gnutls_strerror(err)); + } + + tlsFd->UnderlyingFd = std::move(Fd); + Fd.reset(tlsFd); + return true; +} + /*}}}*/ diff --git a/methods/connect.h b/methods/connect.h index bbe1bb35d..4456d660d 100644 --- a/methods/connect.h +++ b/methods/connect.h @@ -10,12 +10,41 @@ #ifndef CONNECT_H #define CONNECT_H +#include <memory> +#include <stddef.h> #include <string> -class pkgAcqMethod; +class aptMethod; + +/** + * \brief Small representation of a file descriptor for network traffic. + * + * This provides support for TLS, SOCKS, and HTTP CONNECT proxies. + */ +struct MethodFd +{ + /// \brief Returns -1 for unusable, or an fd to select() on otherwise + virtual int Fd() = 0; + /// \brief Should behave like read(2) + virtual ssize_t Read(void *buf, size_t count) = 0; + /// \brief Should behave like write(2) + virtual ssize_t Write(void *buf, size_t count) = 0; + /// \brief Closes the file descriptor. Can be called multiple times. + virtual int Close() = 0; + /// \brief Destructor + virtual ~MethodFd(){}; + /// \brief Construct a MethodFd from a UNIX file descriptor + static std::unique_ptr<MethodFd> FromFd(int iFd); + /// \brief If there is pending data. + virtual bool HasPending(); +}; + +bool Connect(std::string To, int Port, const char *Service, int DefPort, + std::unique_ptr<MethodFd> &Fd, unsigned long TimeOut, aptMethod *Owner); + +bool UnwrapSocks(std::string To, int Port, URI Proxy, std::unique_ptr<MethodFd> &Fd, unsigned long Timeout, aptMethod *Owner); +bool UnwrapTLS(std::string To, std::unique_ptr<MethodFd> &Fd, unsigned long Timeout, aptMethod *Owner); -bool Connect(std::string To,int Port,const char *Service,int DefPort, - int &Fd,unsigned long TimeOut,pkgAcqMethod *Owner); void RotateDNS(); #endif diff --git a/methods/ftp.cc b/methods/ftp.cc index d789637a8..f4986f648 100644 --- a/methods/ftp.cc +++ b/methods/ftp.cc @@ -73,8 +73,8 @@ time_t FtpMethod::FailTime = 0; // FTPConn::FTPConn - Constructor /*{{{*/ // --------------------------------------------------------------------- /* */ -FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1), - DataListenFd(-1), ServerName(Srv), +FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(MethodFd::FromFd(-1)), DataFd(-1), + DataListenFd(-1), ServerName(Srv), ForceExtended(false), TryPassive(true), PeerAddrLen(0), ServerAddrLen(0) { @@ -96,8 +96,7 @@ FTPConn::~FTPConn() /* Just tear down the socket and data socket */ void FTPConn::Close() { - close(ServerFd); - ServerFd = -1; + ServerFd->Close(); close(DataFd); DataFd = -1; close(DataListenFd); @@ -112,10 +111,10 @@ void FTPConn::Close() // --------------------------------------------------------------------- /* Connect to the server using a non-blocking connection and perform a login. */ -bool FTPConn::Open(pkgAcqMethod *Owner) +bool FTPConn::Open(aptMethod *Owner) { // Use the already open connection if possible. - if (ServerFd != -1) + if (ServerFd->Fd() != -1) return true; Close(); @@ -178,12 +177,12 @@ bool FTPConn::Open(pkgAcqMethod *Owner) // Get the remote server's address PeerAddrLen = sizeof(PeerAddr); - if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0) + if (getpeername(ServerFd->Fd(), (sockaddr *)&PeerAddr, &PeerAddrLen) != 0) return _error->Errno("getpeername",_("Unable to determine the peer name")); // Get the local machine's address ServerAddrLen = sizeof(ServerAddr); - if (getsockname(ServerFd,(sockaddr *)&ServerAddr,&ServerAddrLen) != 0) + if (getsockname(ServerFd->Fd(), (sockaddr *)&ServerAddr, &ServerAddrLen) != 0) return _error->Errno("getsockname",_("Unable to determine the local name")); return Res; @@ -314,7 +313,7 @@ bool FTPConn::Login() /* This performs a very simple buffered read. */ bool FTPConn::ReadLine(string &Text) { - if (ServerFd == -1) + if (ServerFd->Fd() == -1) return false; // Suck in a line @@ -339,14 +338,14 @@ bool FTPConn::ReadLine(string &Text) } // Wait for some data.. - if (WaitFd(ServerFd,false,TimeOut) == false) + if (WaitFd(ServerFd->Fd(), false, TimeOut) == false) { Close(); return _error->Error(_("Connection timeout")); } // Suck it back - int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len); + int Res = ServerFd->Read(Buffer + Len, sizeof(Buffer) - Len); if (Res == 0) _error->Error(_("Server closed the connection")); if (Res <= 0) @@ -451,13 +450,13 @@ bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...) unsigned long Start = 0; while (Len != 0) { - if (WaitFd(ServerFd,true,TimeOut) == false) + if (WaitFd(ServerFd->Fd(), true, TimeOut) == false) { Close(); return _error->Error(_("Connection timeout")); } - - int Res = write(ServerFd,S + Start,Len); + + int Res = ServerFd->Write(S + Start, Len); if (Res <= 0) { _error->Errno("write",_("Write error")); diff --git a/methods/ftp.h b/methods/ftp.h index 6a12475a0..5b23dba41 100644 --- a/methods/ftp.h +++ b/methods/ftp.h @@ -10,8 +10,9 @@ #ifndef APT_FTP_H #define APT_FTP_H -#include <apt-pkg/strutl.h> #include "aptmethod.h" +#include "connect.h" +#include <apt-pkg/strutl.h> #include <sys/socket.h> #include <sys/types.h> @@ -22,7 +23,7 @@ class FTPConn { char Buffer[1024*10]; unsigned long Len; - int ServerFd; + std::unique_ptr<MethodFd> ServerFd; int DataFd; int DataListenFd; URI ServerName; @@ -55,7 +56,7 @@ class FTPConn bool WriteMsg(unsigned int &Ret,std::string &Text,const char *Fmt,...); // Connection control - bool Open(pkgAcqMethod *Owner); + bool Open(aptMethod *Owner); void Close(); bool GoPasv(); bool ExtGoPasv(); diff --git a/methods/http.cc b/methods/http.cc index 9f5959548..b302c896d 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -95,7 +95,7 @@ void CircleBuf::Reset() // --------------------------------------------------------------------- /* This fills up the buffer with as much data as is in the FD, assuming it is non-blocking.. */ -bool CircleBuf::Read(int Fd) +bool CircleBuf::Read(std::unique_ptr<MethodFd> const &Fd) { while (1) { @@ -126,11 +126,11 @@ bool CircleBuf::Read(int Fd) // Write the buffer segment ssize_t Res; if(CircleBuf::BwReadLimit) { - Res = read(Fd,Buf + (InP%Size), - BwReadMax > LeftRead() ? LeftRead() : BwReadMax); + Res = Fd->Read(Buf + (InP % Size), + BwReadMax > LeftRead() ? LeftRead() : BwReadMax); } else - Res = read(Fd,Buf + (InP%Size),LeftRead()); - + Res = Fd->Read(Buf + (InP % Size), LeftRead()); + if(Res > 0 && BwReadLimit > 0) CircleBuf::BwTickReadData += Res; @@ -193,7 +193,7 @@ void CircleBuf::FillOut() // CircleBuf::Write - Write from the buffer into a FD /*{{{*/ // --------------------------------------------------------------------- /* This empties the buffer into the FD. */ -bool CircleBuf::Write(int Fd) +bool CircleBuf::Write(std::unique_ptr<MethodFd> const &Fd) { while (1) { @@ -208,7 +208,7 @@ bool CircleBuf::Write(int Fd) // Write the buffer segment ssize_t Res; - Res = write(Fd,Buf + (OutP%Size),LeftWrite()); + Res = Fd->Write(Buf + (OutP % Size), LeftWrite()); if (Res == 0) return false; @@ -291,34 +291,17 @@ CircleBuf::~CircleBuf() HttpServerState::HttpServerState(URI Srv,HttpMethod *Owner) : ServerState(Srv, Owner), In(Owner, 64*1024), Out(Owner, 4*1024) { TimeOut = Owner->ConfigFindI("Timeout", TimeOut); + ServerFd = MethodFd::FromFd(-1); Reset(); } /*}}}*/ // HttpServerState::Open - Open a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* This opens a connection to the server. */ -static bool TalkToSocksProxy(int const ServerFd, std::string const &Proxy, - char const * const type, bool const ReadWrite, uint8_t * const ToFrom, - unsigned int const Size, unsigned int const Timeout) -{ - if (WaitFd(ServerFd, ReadWrite, Timeout) == false) - return _error->Error("Waiting for the SOCKS proxy %s to %s timed out", URI::SiteOnly(Proxy).c_str(), type); - if (ReadWrite == false) - { - if (FileFd::Read(ServerFd, ToFrom, Size) == false) - return _error->Error("Reading the %s from SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str()); - } - else - { - if (FileFd::Write(ServerFd, ToFrom, Size) == false) - return _error->Error("Writing the %s to SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str()); - } - return true; -} bool HttpServerState::Open() { // Use the already open connection if possible. - if (ServerFd != -1) + if (ServerFd->Fd() != -1) return true; Close(); @@ -360,165 +343,15 @@ bool HttpServerState::Open() if (Proxy.empty() == false) Owner->AddProxyAuth(Proxy, ServerName); + bool tls = ServerName.Access == "https"; if (Proxy.Access == "socks5h") { if (Connect(Proxy.Host, Proxy.Port, "socks", 1080, ServerFd, TimeOut, Owner) == false) return false; - /* We implement a very basic SOCKS5 client here complying mostly to RFC1928 expect - * for not offering GSSAPI auth which is a must (we only do no or user/pass auth). - * We also expect the SOCKS5 server to do hostname lookup (aka socks5h) */ - std::string const ProxyInfo = URI::SiteOnly(Proxy); - Owner->Status(_("Connecting to %s (%s)"),"SOCKS5h proxy",ProxyInfo.c_str()); - auto const Timeout = Owner->ConfigFindI("TimeOut", 120); - #define APT_WriteOrFail(TYPE, DATA, LENGTH) if (TalkToSocksProxy(ServerFd, ProxyInfo, TYPE, true, DATA, LENGTH, Timeout) == false) return false - #define APT_ReadOrFail(TYPE, DATA, LENGTH) if (TalkToSocksProxy(ServerFd, ProxyInfo, TYPE, false, DATA, LENGTH, Timeout) == false) return false - if (ServerName.Host.length() > 255) - return _error->Error("Can't use SOCKS5h as hostname %s is too long!", ServerName.Host.c_str()); - if (Proxy.User.length() > 255 || Proxy.Password.length() > 255) - return _error->Error("Can't use user&pass auth as they are too long (%lu and %lu) for the SOCKS5!", Proxy.User.length(), Proxy.Password.length()); - if (Proxy.User.empty()) - { - uint8_t greeting[] = { 0x05, 0x01, 0x00 }; - APT_WriteOrFail("greet-1", greeting, sizeof(greeting)); - } - else - { - uint8_t greeting[] = { 0x05, 0x02, 0x00, 0x02 }; - APT_WriteOrFail("greet-2", greeting, sizeof(greeting)); - } - uint8_t greeting[2]; - APT_ReadOrFail("greet back", greeting, sizeof(greeting)); - if (greeting[0] != 0x05) - return _error->Error("SOCKS proxy %s greets back with wrong version: %d", ProxyInfo.c_str(), greeting[0]); - if (greeting[1] == 0x00) - ; // no auth has no method-dependent sub-negotiations - else if (greeting[1] == 0x02) - { - if (Proxy.User.empty()) - return _error->Error("SOCKS proxy %s negotiated user&pass auth, but we had not offered it!", ProxyInfo.c_str()); - // user&pass auth sub-negotiations are defined by RFC1929 - std::vector<uint8_t> auth = {{ 0x01, static_cast<uint8_t>(Proxy.User.length()) }}; - std::copy(Proxy.User.begin(), Proxy.User.end(), std::back_inserter(auth)); - auth.push_back(static_cast<uint8_t>(Proxy.Password.length())); - std::copy(Proxy.Password.begin(), Proxy.Password.end(), std::back_inserter(auth)); - APT_WriteOrFail("user&pass auth", auth.data(), auth.size()); - uint8_t authstatus[2]; - APT_ReadOrFail("auth report", authstatus, sizeof(authstatus)); - if (authstatus[0] != 0x01) - return _error->Error("SOCKS proxy %s auth status response with wrong version: %d", ProxyInfo.c_str(), authstatus[0]); - if (authstatus[1] != 0x00) - return _error->Error("SOCKS proxy %s reported authorization failure: username or password incorrect? (%d)", ProxyInfo.c_str(), authstatus[1]); - } - else - return _error->Error("SOCKS proxy %s greets back having not found a common authorization method: %d", ProxyInfo.c_str(), greeting[1]); - union { uint16_t * i; uint8_t * b; } portu; - uint16_t port = htons(static_cast<uint16_t>(ServerName.Port == 0 ? 80 : ServerName.Port)); - portu.i = &port; - std::vector<uint8_t> request = {{ 0x05, 0x01, 0x00, 0x03, static_cast<uint8_t>(ServerName.Host.length()) }}; - std::copy(ServerName.Host.begin(), ServerName.Host.end(), std::back_inserter(request)); - request.push_back(portu.b[0]); - request.push_back(portu.b[1]); - APT_WriteOrFail("request", request.data(), request.size()); - uint8_t response[4]; - APT_ReadOrFail("first part of response", response, sizeof(response)); - if (response[0] != 0x05) - return _error->Error("SOCKS proxy %s response with wrong version: %d", ProxyInfo.c_str(), response[0]); - if (response[2] != 0x00) - return _error->Error("SOCKS proxy %s has unexpected non-zero reserved field value: %d", ProxyInfo.c_str(), response[2]); - std::string bindaddr; - if (response[3] == 0x01) // IPv4 address - { - uint8_t ip4port[6]; - APT_ReadOrFail("IPv4+Port of response", ip4port, sizeof(ip4port)); - portu.b[0] = ip4port[4]; - portu.b[1] = ip4port[5]; - port = ntohs(*portu.i); - strprintf(bindaddr, "%d.%d.%d.%d:%d", ip4port[0], ip4port[1], ip4port[2], ip4port[3], port); - } - else if (response[3] == 0x03) // hostname - { - uint8_t namelength; - APT_ReadOrFail("hostname length of response", &namelength, 1); - uint8_t hostname[namelength + 2]; - APT_ReadOrFail("hostname of response", hostname, sizeof(hostname)); - portu.b[0] = hostname[namelength]; - portu.b[1] = hostname[namelength + 1]; - port = ntohs(*portu.i); - hostname[namelength] = '\0'; - strprintf(bindaddr, "%s:%d", hostname, port); - } - else if (response[3] == 0x04) // IPv6 address - { - uint8_t ip6port[18]; - APT_ReadOrFail("IPv6+port of response", ip6port, sizeof(ip6port)); - portu.b[0] = ip6port[16]; - portu.b[1] = ip6port[17]; - port = ntohs(*portu.i); - strprintf(bindaddr, "[%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X]:%d", - ip6port[0], ip6port[1], ip6port[2], ip6port[3], ip6port[4], ip6port[5], ip6port[6], ip6port[7], - ip6port[8], ip6port[9], ip6port[10], ip6port[11], ip6port[12], ip6port[13], ip6port[14], ip6port[15], - port); - } - else - return _error->Error("SOCKS proxy %s destination address is of unknown type: %d", - ProxyInfo.c_str(), response[3]); - if (response[1] != 0x00) - { - char const * errstr = nullptr; - auto errcode = response[1]; - // Tor error reporting can be a bit arcane, lets try to detect & fix it up - if (bindaddr == "0.0.0.0:0") - { - auto const lastdot = ServerName.Host.rfind('.'); - if (lastdot == std::string::npos || ServerName.Host.substr(lastdot) != ".onion") - ; - else if (errcode == 0x01) - { - auto const prevdot = ServerName.Host.rfind('.', lastdot - 1); - if (lastdot == 16 && prevdot == std::string::npos) - ; // valid .onion address - else if (prevdot != std::string::npos && (lastdot - prevdot) == 17) - ; // valid .onion address with subdomain(s) - else - { - errstr = "Invalid hostname: onion service name must be 16 characters long"; - Owner->SetFailReason("SOCKS"); - } - } - // in all likelihood the service is either down or the address has - // a typo and so "Host unreachable" is the better understood error - // compared to the technically correct "TLL expired". - else if (errcode == 0x06) - errcode = 0x04; - } - if (errstr == nullptr) - { - switch (errcode) - { - case 0x01: errstr = "general SOCKS server failure"; Owner->SetFailReason("SOCKS"); break; - case 0x02: errstr = "connection not allowed by ruleset"; Owner->SetFailReason("SOCKS"); break; - case 0x03: errstr = "Network unreachable"; Owner->SetFailReason("ConnectionTimedOut"); break; - case 0x04: errstr = "Host unreachable"; Owner->SetFailReason("ConnectionTimedOut"); break; - case 0x05: errstr = "Connection refused"; Owner->SetFailReason("ConnectionRefused"); break; - case 0x06: errstr = "TTL expired"; Owner->SetFailReason("Timeout"); break; - case 0x07: errstr = "Command not supported"; Owner->SetFailReason("SOCKS"); break; - case 0x08: errstr = "Address type not supported"; Owner->SetFailReason("SOCKS"); break; - default: errstr = "Unknown error"; Owner->SetFailReason("SOCKS"); break; - } - } - return _error->Error("SOCKS proxy %s could not connect to %s (%s) due to: %s (%d)", - ProxyInfo.c_str(), ServerName.Host.c_str(), bindaddr.c_str(), errstr, response[1]); - } - else if (Owner->DebugEnabled()) - ioprintf(std::clog, "http: SOCKS proxy %s connection established to %s (%s)\n", - ProxyInfo.c_str(), ServerName.Host.c_str(), bindaddr.c_str()); - - if (WaitFd(ServerFd, true, Timeout) == false) - return _error->Error("SOCKS proxy %s reported connection to %s (%s), but timed out", - ProxyInfo.c_str(), ServerName.Host.c_str(), bindaddr.c_str()); - #undef APT_ReadOrFail - #undef APT_WriteOrFail + if (UnwrapSocks(ServerName.Host, ServerName.Port == 0 ? 80 : ServerName.Port, + Proxy, ServerFd, Owner->ConfigFindI("TimeOut", 120), Owner) == false) + return false; } else { @@ -539,8 +372,13 @@ bool HttpServerState::Open() Port = Proxy.Port; Host = Proxy.Host; } - return Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner); + if (!Connect(Host, Port, tls ? "https" : "http", tls ? 443 : 80, ServerFd, TimeOut, Owner)) + return false; } + + if (tls && UnwrapTLS(ServerName.Host, ServerFd, TimeOut, Owner) == false) + return false; + return true; } /*}}}*/ @@ -549,8 +387,7 @@ bool HttpServerState::Open() /* */ bool HttpServerState::Close() { - close(ServerFd); - ServerFd = -1; + ServerFd->Close(); return true; } /*}}}*/ @@ -672,7 +509,7 @@ bool HttpServerState::WriteResponse(const std::string &Data) /*{{{*/ /*}}}*/ APT_PURE bool HttpServerState::IsOpen() /*{{{*/ { - return (ServerFd != -1); + return (ServerFd->Fd() != -1); } /*}}}*/ bool HttpServerState::InitHashes(HashStringList const &ExpectedHashes) /*{{{*/ @@ -685,7 +522,7 @@ bool HttpServerState::InitHashes(HashStringList const &ExpectedHashes) /*{{{*/ void HttpServerState::Reset() /*{{{*/ { ServerState::Reset(); - ServerFd = -1; + ServerFd->Close(); } /*}}}*/ @@ -710,7 +547,7 @@ bool HttpServerState::Die(RequestState &Req) SetNonBlock(Req.File.Fd(),false); while (In.WriteSpace() == true) { - if (In.Write(Req.File.Fd()) == false) + if (In.Write(MethodFd::FromFd(Req.File.Fd())) == false) return _error->Errno("write",_("Error writing to the file")); // Done @@ -762,7 +599,7 @@ bool HttpServerState::Flush(FileFd * const File) while (In.WriteSpace() == true) { - if (In.Write(File->Fd()) == false) + if (In.Write(MethodFd::FromFd(File->Fd())) == false) return _error->Errno("write",_("Error writing to file")); if (In.IsLimit() == true) return true; @@ -781,38 +618,45 @@ bool HttpServerState::Flush(FileFd * const File) bool HttpServerState::Go(bool ToFile, RequestState &Req) { // Server has closed the connection - if (ServerFd == -1 && (In.WriteSpace() == false || - ToFile == false)) + if (ServerFd->Fd() == -1 && (In.WriteSpace() == false || + ToFile == false)) return false; - + + // Handle server IO + if (ServerFd->HasPending() && In.ReadSpace() == true) + { + errno = 0; + if (In.Read(ServerFd) == false) + return Die(Req); + } + fd_set rfds,wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); /* Add the server. We only send more requests if the connection will be persisting */ - if (Out.WriteSpace() == true && ServerFd != -1 - && Persistent == true) - FD_SET(ServerFd,&wfds); - if (In.ReadSpace() == true && ServerFd != -1) - FD_SET(ServerFd,&rfds); - + if (Out.WriteSpace() == true && ServerFd->Fd() != -1 && Persistent == true) + FD_SET(ServerFd->Fd(), &wfds); + if (In.ReadSpace() == true && ServerFd->Fd() != -1) + FD_SET(ServerFd->Fd(), &rfds); + // Add the file - int FileFD = -1; + auto FileFD = MethodFd::FromFd(-1); if (Req.File.IsOpen()) - FileFD = Req.File.Fd(); - - if (In.WriteSpace() == true && ToFile == true && FileFD != -1) - FD_SET(FileFD,&wfds); + FileFD = MethodFd::FromFd(Req.File.Fd()); + + if (In.WriteSpace() == true && ToFile == true && FileFD->Fd() != -1) + FD_SET(FileFD->Fd(), &wfds); // Add stdin if (Owner->ConfigFindB("DependOnSTDIN", true) == true) FD_SET(STDIN_FILENO,&rfds); // Figure out the max fd - int MaxFd = FileFD; - if (MaxFd < ServerFd) - MaxFd = ServerFd; + int MaxFd = FileFD->Fd(); + if (MaxFd < ServerFd->Fd()) + MaxFd = ServerFd->Fd(); // Select struct timeval tv; @@ -833,14 +677,14 @@ bool HttpServerState::Go(bool ToFile, RequestState &Req) } // Handle server IO - if (ServerFd != -1 && FD_ISSET(ServerFd,&rfds)) + if (ServerFd->Fd() != -1 && FD_ISSET(ServerFd->Fd(), &rfds)) { errno = 0; if (In.Read(ServerFd) == false) return Die(Req); } - - if (ServerFd != -1 && FD_ISSET(ServerFd,&wfds)) + + if (ServerFd->Fd() != -1 && FD_ISSET(ServerFd->Fd(), &wfds)) { errno = 0; if (Out.Write(ServerFd) == false) @@ -848,7 +692,7 @@ bool HttpServerState::Go(bool ToFile, RequestState &Req) } // Send data to the file - if (FileFD != -1 && FD_ISSET(FileFD,&wfds)) + if (FileFD->Fd() != -1 && FD_ISSET(FileFD->Fd(), &wfds)) { if (In.Write(FileFD) == false) return _error->Errno("write",_("Error writing to output file")); diff --git a/methods/http.h b/methods/http.h index c79a6454e..3336fb780 100644 --- a/methods/http.h +++ b/methods/http.h @@ -13,11 +13,13 @@ #include <apt-pkg/strutl.h> +#include <iostream> +#include <memory> #include <string> #include <sys/time.h> -#include <iostream> #include "basehttp.h" +#include "connect.h" using std::cout; using std::endl; @@ -66,11 +68,11 @@ class CircleBuf unsigned long long TotalWriten; // Read data in - bool Read(int Fd); + bool Read(std::unique_ptr<MethodFd> const &Fd); bool Read(std::string const &Data); // Write data out - bool Write(int Fd); + bool Write(std::unique_ptr<MethodFd> const &Fd); bool WriteTillEl(std::string &Data,bool Single = false); // Control the write limit @@ -95,7 +97,7 @@ struct HttpServerState: public ServerState // This is the connection itself. Output is data FROM the server CircleBuf In; CircleBuf Out; - int ServerFd; + std::unique_ptr<MethodFd> ServerFd; protected: virtual bool ReadHeaderLines(std::string &Data) APT_OVERRIDE; diff --git a/methods/http_main.cc b/methods/http_main.cc index 1e56044b7..90a0450e2 100644 --- a/methods/http_main.cc +++ b/methods/http_main.cc @@ -11,7 +11,7 @@ int main(int, const char *argv[]) // closes the connection (this is dealt with via ServerDie()) signal(SIGPIPE, SIG_IGN); std::string Binary = flNotDir(argv[0]); - if (Binary.find('+') == std::string::npos && Binary != "http") + if (Binary.find('+') == std::string::npos && Binary != "https" && Binary != "http") Binary.append("+http"); return HttpMethod(std::move(Binary)).Loop(); } diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt index 258c9b050..2630a2f89 100644 --- a/po/CMakeLists.txt +++ b/po/CMakeLists.txt @@ -10,12 +10,18 @@ apt_add_translation_domain( EXCLUDE_LANGUAGES ${languages_excluded} ) +if (HAVE_CURL) + set(curl_methods https) +else() + set(curl_methods) +endif() + apt_add_translation_domain( DOMAIN apt TARGETS apt apt-cache apt-get apt-config apt-cdrom apt-helper apt-mark apt-private # Methods - file copy store gpgv cdrom http https ftp rred rsh mirror + file copy store gpgv cdrom http ${curl_methods} ftp rred rsh mirror SCRIPTS ../dselect/install ../dselect/update EXCLUDE_LANGUAGES ${languages_excluded} ) diff --git a/shippable.yml b/shippable.yml index ad72afc80..e144d94d5 100644 --- a/shippable.yml +++ b/shippable.yml @@ -9,7 +9,7 @@ build: - apt-get install -qq build-essential - ./prepare-release travis-ci - mkdir build - - ( cd build && cmake .. ) + - ( cd build && cmake -DWITH_CURL=OFF .. ) - make -C build -j 4 - CTEST_OUTPUT_ON_FAILURE=1 make -C build test - ./test/integration/run-tests -q diff --git a/test/integration/test-apt-https-no-redirect b/test/integration/test-apt-https-no-redirect index d6c630d5f..05e97159c 100755 --- a/test/integration/test-apt-https-no-redirect +++ b/test/integration/test-apt-https-no-redirect @@ -14,7 +14,7 @@ echo 'alright' > aptarchive/working changetohttpswebserver webserverconfig 'aptwebserver::redirect::replace::/redirectme/' "http://localhost:${APTHTTPPORT}/" webserverconfig 'aptwebserver::redirect::replace::/redirectme2/' "https://localhost:${APTHTTPSPORT}/" -echo 'Dir::Bin::Methods::https+http "https";' > rootdir/etc/apt/apt.conf.d/99add-https-http-method +echo 'Dir::Bin::Methods::https+http "http";' > rootdir/etc/apt/apt.conf.d/99add-https-http-method msgtest 'download of a file works via' 'http' testsuccess --nomsg downloadfile "http://localhost:${APTHTTPPORT}/working" httpfile @@ -26,13 +26,9 @@ testfileequal httpsfile 'alright' rm -f httpfile httpsfile msgtest 'download of http file works via' 'https+http' -testsuccess --nomsg downloadfile "http://localhost:${APTHTTPPORT}/working" httpfile +testsuccess --nomsg downloadfile "https+http://localhost:${APTHTTPPORT}/working" httpfile testfileequal httpfile 'alright' - -msgtest 'download of https file works via' 'https+http' -testsuccess --nomsg downloadfile "https://localhost:${APTHTTPSPORT}/working" httpsfile -testfileequal httpsfile 'alright' -rm -f httpfile httpsfile +rm -f httpfile msgtest 'download of a file does not work if' 'https redirected to http' testfailure --nomsg downloadfile "https://localhost:${APTHTTPSPORT}/redirectme/working" redirectfile diff --git a/test/integration/test-apt-update-failure-propagation b/test/integration/test-apt-update-failure-propagation index 1361b1b93..9ca6e481f 100755 --- a/test/integration/test-apt-update-failure-propagation +++ b/test/integration/test-apt-update-failure-propagation @@ -100,7 +100,13 @@ for FILE in rootdir/etc/apt/sources.list.d/*-stable-* ; do sed -i -e "s#:${APTHTTPSPORT}/#:666/#" "$FILE" done testwarning aptget update -o Dir::Bin::Methods::https="${OLDMETHODS}/https" +if grep -q WITH_CURL:BOOL=OFF $PROJECT_BINARY_DIR/CMakeCache.txt; then +testequalor2 "W: Failed to fetch https://localhost:666/dists/stable/InRelease Failed to connect to localhost port 666: Connection refused +W: Some index files failed to download. They have been ignored, or old ones used instead." "W: Failed to fetch https://localhost:666/dists/stable/InRelease Could not connect to localhost:666 (127.0.0.1). - connect (111: Connection refused) +W: Some index files failed to download. They have been ignored, or old ones used instead." tail -n 2 rootdir/tmp/testwarning.output +else testequalor2 "W: Failed to fetch https://localhost:666/dists/stable/InRelease Failed to connect to localhost port 666: Connection refused W: Some index files failed to download. They have been ignored, or old ones used instead." "W: Failed to fetch https://localhost:666/dists/stable/InRelease couldn't connect to host W: Some index files failed to download. They have been ignored, or old ones used instead." tail -n 2 rootdir/tmp/testwarning.output +fi posttest |