diff options
author | Jaywalker <jwilliams@nsllc.com> | 2018-02-07 23:59:28 -0600 |
---|---|---|
committer | Sam Bingner <sam@bingner.com> | 2019-08-18 12:07:59 -1000 |
commit | d19e6145e8fb367dfb74a1ad7f02d4df89678481 (patch) | |
tree | f3a721d4fd0b1c9d417426ed54d7628d62da4473 /methods/http.cc | |
parent | bd485f75da432212d41f8c3b5cfbceb0e28d4fbb (diff) |
Added kirb's TLS1.2 patch
Diffstat (limited to 'methods/http.cc')
-rw-r--r-- | methods/http.cc | 1355 |
1 files changed, 458 insertions, 897 deletions
diff --git a/methods/http.cc b/methods/http.cc index d75e9fe84..d1f6026a3 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -16,7 +16,7 @@ ##################################################################### */ /*}}}*/ // Include Files /*{{{*/ -#include <config.h> +// #include <config.h> #include <apt-pkg/configuration.h> #include <apt-pkg/error.h> @@ -36,6 +36,7 @@ #include <stdio.h> #include <stdlib.h> #include <sys/select.h> +#include <sys/sysctl.h> #include <sys/stat.h> #include <sys/time.h> #include <unistd.h> @@ -44,984 +45,507 @@ #include "connect.h" #include "http.h" -#include <apti18n.h> +#include <netdb.h> +#include <dlfcn.h> +#include <lockdown.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CFNetwork/CFNetwork.h> +extern "C" CFDictionaryRef SCDynamicStoreCopyProxies(void *); /*}}}*/ using namespace std; -unsigned long long CircleBuf::BwReadLimit=0; -unsigned long long CircleBuf::BwTickReadData=0; -std::chrono::steady_clock::duration CircleBuf::BwReadTick{0}; -const unsigned int CircleBuf::BW_HZ=10; - -// CircleBuf::CircleBuf - Circular input buffer /*{{{*/ -// --------------------------------------------------------------------- -/* */ -CircleBuf::CircleBuf(HttpMethod const * const Owner, unsigned long long Size) - : Size(Size), Hash(NULL), TotalWriten(0) -{ - Buf = new unsigned char[Size]; - Reset(); - - CircleBuf::BwReadLimit = Owner->ConfigFindI("Dl-Limit", 0) * 1024; -} - /*}}}*/ -// CircleBuf::Reset - Reset to the default state /*{{{*/ -// --------------------------------------------------------------------- -/* */ -void CircleBuf::Reset() -{ - InP = 0; - OutP = 0; - StrPos = 0; - TotalWriten = 0; - MaxGet = (unsigned long long)-1; - OutQueue = string(); - if (Hash != NULL) - { - delete Hash; - Hash = NULL; +#define _(str) str + +CFStringRef Firmware_; +const char *Machine_; +CFStringRef UniqueID_; + +void CfrsError(const char *name, CFReadStreamRef rs) { + CFStreamError se = CFReadStreamGetError(rs); + + if (se.domain == kCFStreamErrorDomainCustom) { + } else if (se.domain == kCFStreamErrorDomainPOSIX) { + _error->Error("POSIX: %s", strerror(se.error)); + } else if (se.domain == kCFStreamErrorDomainMacOSStatus) { + _error->Error("MacOSStatus: %d", (int)se.error); + } else if (se.domain == kCFStreamErrorDomainNetDB) { + _error->Error("NetDB: %s %s", name, gai_strerror(se.error)); + } else if (se.domain == kCFStreamErrorDomainMach) { + _error->Error("Mach: %d", (int)se.error); + } else if (se.domain == kCFStreamErrorDomainHTTP) { + switch (se.error) { + case kCFStreamErrorHTTPParseFailure: + _error->Error("Parse failure"); + break; + + case kCFStreamErrorHTTPRedirectionLoop: + _error->Error("Redirection loop"); + break; + + case kCFStreamErrorHTTPBadURL: + _error->Error("Bad URL"); + break; + + default: + _error->Error("Unknown HTTP error: %d", (int)se.error); + break; + } + } else if (se.domain == kCFStreamErrorDomainSOCKS) { + _error->Error("SOCKS: %d", (int)se.error); + } else if (se.domain == kCFStreamErrorDomainSystemConfiguration) { + _error->Error("SystemConfiguration: %d", (int)se.error); + } else if (se.domain == kCFStreamErrorDomainSSL) { + _error->Error("SSL: %d", (int)se.error); + } else { + _error->Error("Domain #%d: %d", (int)se.domain, (int)se.error); } } - /*}}}*/ -// CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/ -// --------------------------------------------------------------------- -/* This fills up the buffer with as much data as is in the FD, assuming it - is non-blocking.. */ -bool CircleBuf::Read(std::unique_ptr<MethodFd> const &Fd) -{ - while (1) - { - // Woops, buffer is full - if (InP - OutP == Size) - return true; - - // what's left to read in this tick - unsigned long long const BwReadMax = CircleBuf::BwReadLimit/BW_HZ; - - if(CircleBuf::BwReadLimit) { - auto const now = std::chrono::steady_clock::now().time_since_epoch(); - auto const d = now - CircleBuf::BwReadTick; - - auto const tickLen = std::chrono::microseconds(std::chrono::seconds(1)) / BW_HZ; - if(d > tickLen) { - CircleBuf::BwReadTick = now; - CircleBuf::BwTickReadData = 0; - } - - if(CircleBuf::BwTickReadData >= BwReadMax) { - usleep(tickLen.count()); - return true; - } - } - // Write the buffer segment - ssize_t Res; - if(CircleBuf::BwReadLimit) { - Res = Fd->Read(Buf + (InP % Size), - BwReadMax > LeftRead() ? LeftRead() : BwReadMax); - } else - Res = Fd->Read(Buf + (InP % Size), LeftRead()); +unsigned long TimeOut = 120; - if(Res > 0 && BwReadLimit > 0) - CircleBuf::BwTickReadData += Res; - - if (Res == 0) - return false; - if (Res < 0) - { - if (errno == EAGAIN) - return true; - return false; - } +static const CFOptionFlags kNetworkEvents = + kCFStreamEventOpenCompleted | + kCFStreamEventHasBytesAvailable | + kCFStreamEventEndEncountered | + kCFStreamEventErrorOccurred | +0; - InP += Res; - } -} - /*}}}*/ -// CircleBuf::Read - Put the string into the buffer /*{{{*/ -// --------------------------------------------------------------------- -/* This will hold the string in and fill the buffer with it as it empties */ -bool CircleBuf::Read(string const &Data) -{ - OutQueue.append(Data); - FillOut(); - return true; -} - /*}}}*/ -// CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/ -// --------------------------------------------------------------------- -/* */ -void CircleBuf::FillOut() -{ - if (OutQueue.empty() == true) - return; - while (1) - { - // Woops, buffer is full - if (InP - OutP == Size) - return; - - // Write the buffer segment - unsigned long long Sz = LeftRead(); - if (OutQueue.length() - StrPos < Sz) - Sz = OutQueue.length() - StrPos; - memcpy(Buf + (InP%Size),OutQueue.c_str() + StrPos,Sz); - - // Advance - StrPos += Sz; - InP += Sz; - if (OutQueue.length() == StrPos) - { - StrPos = 0; - OutQueue = ""; - return; - } +static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType event, void *arg) { + switch (event) { + case kCFStreamEventOpenCompleted: + break; + + case kCFStreamEventHasBytesAvailable: + case kCFStreamEventEndEncountered: + *reinterpret_cast<int *>(arg) = 1; + CFRunLoopStop(CFRunLoopGetCurrent()); + break; + + case kCFStreamEventErrorOccurred: + *reinterpret_cast<int *>(arg) = -1; + CFRunLoopStop(CFRunLoopGetCurrent()); + break; } } - /*}}}*/ -// CircleBuf::Write - Write from the buffer into a FD /*{{{*/ -// --------------------------------------------------------------------- -/* This empties the buffer into the FD. */ -bool CircleBuf::Write(std::unique_ptr<MethodFd> const &Fd) -{ - while (1) - { - FillOut(); - - // Woops, buffer is empty - if (OutP == InP) - return true; - - if (OutP == MaxGet) - return true; - - // Write the buffer segment - ssize_t Res; - Res = Fd->Write(Buf + (OutP % Size), LeftWrite()); - if (Res == 0) - return false; - if (Res < 0) - { - if (errno == EAGAIN) - return true; - - return false; - } +/* http://lists.apple.com/archives/Macnetworkprog/2006/Apr/msg00014.html */ +int CFReadStreamOpen(CFReadStreamRef stream, double timeout) { + CFStreamClientContext context; + int value(0); - TotalWriten += Res; - - if (Hash != NULL) - Hash->Add(Buf + (OutP%Size),Res); - - OutP += Res; + memset(&context, 0, sizeof(context)); + context.info = &value; + + if (CFReadStreamSetClient(stream, kNetworkEvents, CFReadStreamCallback, &context)) { + CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + if (CFReadStreamOpen(stream)) + CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false); + else + value = -1; + CFReadStreamSetClient(stream, kCFStreamEventNone, NULL, NULL); } + + return value; } - /*}}}*/ -// CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/ + +// HttpMethod::SendReq - Send the HTTP request /*{{{*/ // --------------------------------------------------------------------- -/* This copies till the first empty line */ -bool CircleBuf::WriteTillEl(string &Data,bool Single) +/* This places the http request in the outbound buffer */ +void HttpMethod::SendReq(FetchItem *Itm) { - // We cheat and assume it is unneeded to have more than one buffer load - for (unsigned long long I = OutP; I < InP; I++) - { - if (Buf[I%Size] != '\n') - continue; - ++I; - - if (Single == false) - { - if (I < InP && Buf[I%Size] == '\r') - ++I; - if (I >= InP || Buf[I%Size] != '\n') - continue; - ++I; - } - - Data = ""; - while (OutP < I) - { - unsigned long long Sz = LeftWrite(); - if (Sz == 0) - return false; - if (I - OutP < Sz) - Sz = I - OutP; - Data += string((char *)(Buf + (OutP%Size)),Sz); - OutP += Sz; - } - return true; - } - return false; } /*}}}*/ -// CircleBuf::Write - Write from the buffer to a string /*{{{*/ -// --------------------------------------------------------------------- -/* This copies everything */ -bool CircleBuf::Write(string &Data) +std::unique_ptr<ServerState> HttpMethod::CreateServerState(URI const &uri)/*{{{*/ { - Data = std::string((char *)Buf + (OutP % Size), LeftWrite()); - OutP += LeftWrite(); - return true; + return NULL; } /*}}}*/ -CircleBuf::~CircleBuf() /*{{{*/ +void HttpMethod::RotateDNS() /*{{{*/ { - delete [] Buf; - delete Hash; } /*}}}*/ - -// UnwrapHTTPConnect - Does the HTTP CONNECT handshake /*{{{*/ -// --------------------------------------------------------------------- -/* Performs a TLS handshake on the socket */ -struct HttpConnectFd : public MethodFd +BaseHttpMethod::DealWithHeadersResult HttpMethod::DealWithHeaders(FetchResult &Res)/*{{{*/ { - std::unique_ptr<MethodFd> UnderlyingFd; - std::string Buffer; - - int Fd() APT_OVERRIDE { return UnderlyingFd->Fd(); } - - ssize_t Read(void *buf, size_t count) APT_OVERRIDE - { - if (!Buffer.empty()) - { - auto read = count < Buffer.size() ? count : Buffer.size(); + auto ret = BaseHttpMethod::DealWithHeaders(Res); + if (ret != BaseHttpMethod::FILE_IS_OPEN) + return ret; - memcpy(buf, Buffer.data(), read); - Buffer.erase(Buffer.begin(), Buffer.begin() + read); - return read; - } + // Open the file + delete File; + File = new FileFd(Queue->DestFile,FileFd::WriteAny); + if (_error->PendingError() == true) + return ERROR_NOT_FROM_SERVER; - return UnderlyingFd->Read(buf, count); - } - ssize_t Write(void *buf, size_t count) APT_OVERRIDE - { - return UnderlyingFd->Write(buf, count); - } + FailFile = Queue->DestFile; + FailFile.c_str(); // Make sure we don't do a malloc in the signal handler + FailFd = File->Fd(); + FailTime = Server->Date; - int Close() APT_OVERRIDE + if (Server->InitHashes(Queue->ExpectedHashes) == false || Server->AddPartialFileToHashes(*File) == false) { - return UnderlyingFd->Close(); + _error->Errno("read",_("Problem hashing file")); + return ERROR_NOT_FROM_SERVER; } + if (Server->StartPos > 0) + Res.ResumePoint = Server->StartPos; - bool HasPending() APT_OVERRIDE - { - return !Buffer.empty(); - } -}; + SetNonBlock(File->Fd(),true); + return FILE_IS_OPEN; +} -static ResultState UnwrapHTTPConnect(std::string Host, int Port, URI Proxy, std::unique_ptr<MethodFd> &Fd, - unsigned long Timeout, aptAuthConfMethod *Owner) +// HttpMethod::Loop - Main loop /*{{{*/ +int HttpMethod::Loop() { - Owner->Status(_("Connecting to %s (%s)"), "HTTP proxy", URI::SiteOnly(Proxy).c_str()); - // The HTTP server expects a hostname with a trailing :port - std::stringstream Req; - std::string ProperHost; - - if (Host.find(':') != std::string::npos) - ProperHost = '[' + Proxy.Host + ']'; - else - ProperHost = Proxy.Host; - - // Build the connect - Req << "CONNECT " << Host << ":" << std::to_string(Port) << " HTTP/1.1\r\n"; - if (Proxy.Port != 0) - Req << "Host: " << ProperHost << ":" << std::to_string(Proxy.Port) << "\r\n"; - else - Req << "Host: " << ProperHost << "\r\n"; - - Owner->MaybeAddAuthTo(Proxy); - if (Proxy.User.empty() == false || Proxy.Password.empty() == false) - Req << "Proxy-Authorization: Basic " - << Base64Encode(Proxy.User + ":" + Proxy.Password) << "\r\n"; - - Req << "User-Agent: " << Owner->ConfigFind("User-Agent", "Debian APT-HTTP/1.3 (" PACKAGE_VERSION ")") << "\r\n"; - - Req << "\r\n"; - - CircleBuf In(dynamic_cast<HttpMethod *>(Owner), 4096); - CircleBuf Out(dynamic_cast<HttpMethod *>(Owner), 4096); - std::string Headers; - - if (Owner->DebugEnabled() == true) - cerr << Req.str() << endl; - Out.Read(Req.str()); - - // Writing from proxy - while (Out.WriteSpace() > 0) - { - if (WaitFd(Fd->Fd(), true, Timeout) == false) + signal(SIGTERM,SigTerm); + signal(SIGINT,SigTerm); + + Server = 0; + + std::set<std::string> cached; + + int FailCounter = 0; + while (1) + { + // We have no commands, wait for some to arrive + if (Queue == 0) { - _error->Errno("select", "Writing to proxy failed"); - return ResultState::TRANSIENT_ERROR; + if (WaitFd(STDIN_FILENO) == false) + return 0; } - if (Out.Write(Fd) == false) + + /* Run messages, we can accept 0 (no message) if we didn't + do a WaitFd above.. Otherwise the FD is closed. */ + int Result = Run(true); + if (Result != -1 && (Result != 0 || Queue == 0)) { - _error->Errno("write", "Writing to proxy failed"); - return ResultState::TRANSIENT_ERROR; + if(FailReason.empty() == false || + ConfigFindB("DependOnSTDIN", true) == true) + return 100; + else + return 0; } - } - while (In.ReadSpace() > 0) - { - if (WaitFd(Fd->Fd(), false, Timeout) == false) - { - _error->Errno("select", "Reading from proxy failed"); - return ResultState::TRANSIENT_ERROR; - } - if (In.Read(Fd) == false) - { - _error->Errno("read", "Reading from proxy failed"); - return ResultState::TRANSIENT_ERROR; + if (Queue == 0) + continue; + + CFStringEncoding se = kCFStringEncodingUTF8; + + URI uri2 = Queue->Uri; + string uriString = static_cast<string>(uri2); + + char *url = strdup(uriString.c_str()); + url: + URI uri = std::string(url); + std::string hs = uri.Host; + + if (cached.find(hs) != cached.end()) { + _error->Error("Cached Failure"); + Fail(true); + free(url); + FailCounter = 0; + continue; } - if (In.WriteTillEl(Headers)) - break; - } + std::string urs = uri; - if (Owner->DebugEnabled() == true) - cerr << Headers << endl; + for (;;) { + size_t bad = urs.find_first_of("+"); + if (bad == std::string::npos) + break; + // XXX: generalize + urs = urs.substr(0, bad) + "%2b" + urs.substr(bad + 1); + } - if (!(APT::String::Startswith(Headers, "HTTP/1.0 200") || APT::String::Startswith(Headers, "HTTP/1.1 200"))) - { - _error->Error("Invalid response from proxy: %s", Headers.c_str()); - return ResultState::TRANSIENT_ERROR; - } + CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se); + CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL); + CFRelease(sr); + CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1); + CFRelease(ur); + + struct stat SBuf; + if (stat(Queue->DestFile.c_str(), &SBuf) >= 0 && SBuf.st_size > 0) { + sr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%li-"), (long) SBuf.st_size - 1); + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Range"), sr); + CFRelease(sr); + + sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime, false).c_str(), se); + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr); + CFRelease(sr); + + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache")); + } else if (Queue->LastModified != 0) { + sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(Queue->LastModified, true).c_str(), se); + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr); + CFRelease(sr); + + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache")); + } else + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("max-age=0")); - if (In.WriteSpace() > 0) - { - // Maybe there is actual data already read, if so we need to buffer it - std::unique_ptr<HttpConnectFd> NewFd(new HttpConnectFd()); - In.Write(NewFd->Buffer); - NewFd->UnderlyingFd = std::move(Fd); - Fd = std::move(NewFd); - } + if (Firmware_ != NULL) + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Firmware"), Firmware_); - return ResultState::SUCCESSFUL; -} - /*}}}*/ + sr = CFStringCreateWithCString(kCFAllocatorDefault, Machine_, se); + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Machine"), sr); + CFRelease(sr); -// HttpServerState::HttpServerState - Constructor /*{{{*/ -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. */ -ResultState HttpServerState::Open() -{ - // Use the already open connection if possible. - if (ServerFd->Fd() != -1) - return ResultState::SUCCESSFUL; - - Close(); - In.Reset(); - Out.Reset(); - Persistent = true; - - bool tls = (ServerName.Access == "https" || APT::String::Endswith(ServerName.Access, "+https")); - - // Determine the proxy setting - // Used to run AutoDetectProxy(ServerName) here, but we now send a Proxy - // header in the URI Acquire request and set "Acquire::"+uri.Access+"::proxy::"+uri.Host - // to it in BaseHttpMethod::Loop() - string SpecificProxy = Owner->ConfigFind("Proxy::" + ServerName.Host, ""); - if (!SpecificProxy.empty()) - { - if (SpecificProxy == "DIRECT") - Proxy = ""; - else - Proxy = SpecificProxy; - } - else - { - string DefProxy = Owner->ConfigFind("Proxy", ""); - if (!DefProxy.empty()) - { - Proxy = DefProxy; - } - else - { - char *result = getenv("http_proxy"); - Proxy = result ? result : ""; - if (tls == true) - { - char *result = getenv("https_proxy"); - if (result != nullptr) - { - Proxy = result; - } - } - } - } - - // Parse no_proxy, a , separated list of domains - if (getenv("no_proxy") != 0) - { - if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) - Proxy = ""; - } + if (UniqueID_ != NULL) + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Unique-ID"), UniqueID_); - if (Proxy.empty() == false) - Owner->AddProxyAuth(Proxy, ServerName); + CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.592")); - auto const DefaultService = tls ? "https" : "http"; - auto const DefaultPort = tls ? 443 : 80; - if (Proxy.Access == "socks5h") - { - auto result = Connect(Proxy.Host, Proxy.Port, "socks", 1080, ServerFd, TimeOut, Owner); - if (result != ResultState::SUCCESSFUL) - return result; - - result = UnwrapSocks(ServerName.Host, ServerName.Port == 0 ? DefaultPort : ServerName.Port, - Proxy, ServerFd, Owner->ConfigFindI("TimeOut", 30), Owner); - if (result != ResultState::SUCCESSFUL) - return result; - } - else - { - // 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) - { - if (ServerName.Port != 0) - Port = ServerName.Port; - Host = ServerName.Host; - } - else if (Proxy.Access != "http" && Proxy.Access != "https") - { - _error->Error("Unsupported proxy configured: %s", URI::SiteOnly(Proxy).c_str()); - return ResultState::FATAL_ERROR; - } - else - { - if (Proxy.Port != 0) - Port = Proxy.Port; - Host = Proxy.Host; + CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm); + CFRelease(hm); - if (Proxy.Access == "https" && Port == 0) - Port = 443; - } - auto result = Connect(Host, Port, DefaultService, DefaultPort, ServerFd, TimeOut, Owner); - if (result != ResultState::SUCCESSFUL) - return result; - if (Host == Proxy.Host && Proxy.Access == "https") - { - result = UnwrapTLS(Proxy.Host, ServerFd, TimeOut, Owner); - if (result != ResultState::SUCCESSFUL) - return result; - } - if (Host == Proxy.Host && tls) - { - result = UnwrapHTTPConnect(ServerName.Host, ServerName.Port == 0 ? DefaultPort : ServerName.Port, Proxy, ServerFd, Owner->ConfigFindI("TimeOut", 30), Owner); - if (result != ResultState::SUCCESSFUL) - return result; - } - } +#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout") +#define _kCFStreamPropertyWriteTimeout CFSTR("_kCFStreamPropertyWriteTimeout") +#define _kCFStreamPropertySocketImmediateBufferTimeOut CFSTR("_kCFStreamPropertySocketImmediateBufferTimeOut") - if (tls) - return UnwrapTLS(ServerName.Host, ServerFd, TimeOut, Owner); + /*SInt32 to(TimeOut); + CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &to));os_log(OS_LOG_DEFAULT, "[%{public}s:%{public}d]",__BASE_FILE__,__LINE__);*/ + double to = TimeOut; + CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &to)); - return ResultState::SUCCESSFUL; -} - /*}}}*/ -// HttpServerState::Close - Close a connection to the server /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool HttpServerState::Close() -{ - ServerFd->Close(); - return true; -} - /*}}}*/ -// HttpServerState::RunData - Transfer the data from the socket /*{{{*/ -ResultState HttpServerState::RunData(RequestState &Req) -{ - Req.State = RequestState::Data; - - // Chunked transfer encoding is fun.. - if (Req.Encoding == RequestState::Chunked) - { - while (1) - { - // Grab the block size - ResultState Last = ResultState::SUCCESSFUL; - string Data; - In.Limit(-1); - do - { - if (In.WriteTillEl(Data,true) == true) - break; - } while ((Last = Go(false, Req)) == ResultState::SUCCESSFUL); - - if (Last != ResultState::SUCCESSFUL) - return Last; - - // See if we are done - unsigned long long Len = strtoull(Data.c_str(),0,16); - if (Len == 0) - { - In.Limit(-1); - - // We have to remove the entity trailer - Last = ResultState::SUCCESSFUL; - do - { - if (In.WriteTillEl(Data,true) == true && Data.length() <= 2) - break; - } while ((Last = Go(false, Req)) == ResultState::SUCCESSFUL); - return Last; - } - - // Transfer the block - In.Limit(Len); - while (Go(true, Req) == ResultState::SUCCESSFUL) - if (In.IsLimit() == true) - break; - - // Error - if (In.IsLimit() == false) - return ResultState::TRANSIENT_ERROR; - - // The server sends an extra new line before the next block specifier.. - In.Limit(-1); - Last = ResultState::SUCCESSFUL; - do - { - if (In.WriteTillEl(Data,true) == true) - break; - } while ((Last = Go(false, Req)) == ResultState::SUCCESSFUL); - if (Last != ResultState::SUCCESSFUL) - return Last; - } - } - else - { - /* Closes encoding is used when the server did not specify a size, the - loss of the connection means we are done */ - if (Req.JunkSize != 0) - In.Limit(Req.JunkSize); - else if (Req.DownloadSize != 0) - { - if (Req.MaximumSize != 0 && Req.DownloadSize > Req.MaximumSize) - { - Owner->SetFailReason("MaximumSizeExceeded"); - _error->Error(_("File has unexpected size (%llu != %llu). Mirror sync in progress?"), - Req.DownloadSize, Req.MaximumSize); - return ResultState::FATAL_ERROR; - } - In.Limit(Req.DownloadSize); - } - else if (Persistent == false) - In.Limit(-1); + CFReadStreamSetProperty(rs, _kCFStreamPropertyReadTimeout, nm); + CFReadStreamSetProperty(rs, _kCFStreamPropertyWriteTimeout, nm); + CFReadStreamSetProperty(rs, _kCFStreamPropertySocketImmediateBufferTimeOut, nm); + CFRelease(nm); - // Just transfer the whole block. - while (true) - { - if (In.IsLimit() == false) - { - auto const result = Go(true, Req); - if (result == ResultState::SUCCESSFUL) - continue; - return result; - } - - In.Limit(-1); - return _error->PendingError() ? ResultState::FATAL_ERROR : ResultState::SUCCESSFUL; - } - } + CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL); + CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr); + CFRelease(dr); - if (Flush(&Req.File) == false) - return ResultState::TRANSIENT_ERROR; - return ResultState::SUCCESSFUL; -} - /*}}}*/ -ResultState HttpServerState::RunDataToDevNull(RequestState &Req) /*{{{*/ -{ - // no need to clean up if we discard the connection anyhow - if (Persistent == false) - return ResultState::SUCCESSFUL; - Req.File.Open("/dev/null", FileFd::WriteOnly); - return RunData(Req); -} - /*}}}*/ -bool HttpServerState::ReadHeaderLines(std::string &Data) /*{{{*/ -{ - return In.WriteTillEl(Data); -} - /*}}}*/ -ResultState HttpServerState::LoadNextResponse(bool const ToFile, RequestState &Req) /*{{{*/ -{ - return Go(ToFile, Req); -} - /*}}}*/ -bool HttpServerState::WriteResponse(const std::string &Data) /*{{{*/ -{ - return Out.Read(Data); -} - /*}}}*/ -APT_PURE bool HttpServerState::IsOpen() /*{{{*/ -{ - return (ServerFd->Fd() != -1); -} - /*}}}*/ -bool HttpServerState::InitHashes(HashStringList const &ExpectedHashes) /*{{{*/ -{ - delete In.Hash; - In.Hash = new Hashes(ExpectedHashes); - return true; -} - /*}}}*/ -void HttpServerState::Reset() /*{{{*/ -{ - ServerState::Reset(); - ServerFd->Close(); -} - /*}}}*/ + //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue); + CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); -APT_PURE Hashes * HttpServerState::GetHashes() /*{{{*/ -{ - return In.Hash; -} - /*}}}*/ -// HttpServerState::Die - The server has closed the connection. /*{{{*/ -ResultState HttpServerState::Die(RequestState &Req) -{ - unsigned int LErrno = errno; + FetchResult Res; + CFIndex rd; + UInt32 sc; - // Dump the buffer to the file - if (Req.State == RequestState::Data) - { - if (Req.File.IsOpen() == false) - return ResultState::SUCCESSFUL; - // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking - // can't be set - if (Req.File.Name() != "/dev/null") - SetNonBlock(Req.File.Fd(),false); - while (In.WriteSpace() == true) - { - if (In.Write(MethodFd::FromFd(Req.File.Fd())) == false) - { - _error->Errno("write", _("Error writing to the file")); - return ResultState::TRANSIENT_ERROR; - } - - // Done - if (In.IsLimit() == true) - return ResultState::SUCCESSFUL; - } - } + uint8_t data[10240]; + size_t offset = 0; - // See if this is because the server finished the data stream - if (In.IsLimit() == false && Req.State != RequestState::Header && - Persistent == true) - { - Close(); - if (LErrno == 0) - { - _error->Error(_("Error reading from server. Remote end closed connection")); - return ResultState::TRANSIENT_ERROR; - } - errno = LErrno; - _error->Errno("read", _("Error reading from server")); - return ResultState::TRANSIENT_ERROR; - } - else - { - In.Limit(-1); + Status("Connecting to %s", hs.c_str()); - // Nothing left in the buffer - if (In.WriteSpace() == false) - return ResultState::TRANSIENT_ERROR; + switch (CFReadStreamOpen(rs, to)) { + case -1: + CfrsError("Open", rs); + goto fail; - // We may have got multiple responses back in one packet.. - Close(); - return ResultState::SUCCESSFUL; - } + case 0: + _error->Error("Host Unreachable"); + cached.insert(hs); + goto fail; - return ResultState::TRANSIENT_ERROR; -} - /*}}}*/ -// HttpServerState::Flush - Dump the buffer into the file /*{{{*/ -// --------------------------------------------------------------------- -/* This takes the current input buffer from the Server FD and writes it - into the file */ -bool HttpServerState::Flush(FileFd * const File) -{ - if (File != nullptr) - { - // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking - // can't be set - if (File->Name() != "/dev/null") - SetNonBlock(File->Fd(),false); - if (In.WriteSpace() == false) - return true; - - while (In.WriteSpace() == true) - { - if (In.Write(MethodFd::FromFd(File->Fd())) == false) - return _error->Errno("write",_("Error writing to file")); - if (In.IsLimit() == true) - return true; - } + case 1: + /* success */ + break; - if (In.IsLimit() == true || Persistent == false) - return true; - } - return false; -} - /*}}}*/ -// HttpServerState::Go - Run a single loop /*{{{*/ -// --------------------------------------------------------------------- -/* This runs the select loop over the server FDs, Output file FDs and - stdin. */ -ResultState HttpServerState::Go(bool ToFile, RequestState &Req) -{ - // Server has closed the connection - if (ServerFd->Fd() == -1 && (In.WriteSpace() == false || - ToFile == false)) - return ResultState::TRANSIENT_ERROR; + fail: + Fail(true); + goto done; + } - // Handle server IO - if (ServerFd->HasPending() && In.ReadSpace() == true) - { - errno = 0; - if (In.Read(ServerFd) == false) - return Die(Req); - } + rd = CFReadStreamRead(rs, data, sizeof(data)); - 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->Fd() != -1 && Persistent == true) - FD_SET(ServerFd->Fd(), &wfds); - if (In.ReadSpace() == true && ServerFd->Fd() != -1) - FD_SET(ServerFd->Fd(), &rfds); - - // Add the file - auto FileFD = MethodFd::FromFd(-1); - if (Req.File.IsOpen()) - 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->Fd(); - if (MaxFd < ServerFd->Fd()) - MaxFd = ServerFd->Fd(); - - // Select - struct timeval tv; - tv.tv_sec = TimeOut; - tv.tv_usec = 0; - int Res = 0; - if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) - { - if (errno == EINTR) - return ResultState::SUCCESSFUL; - _error->Errno("select", _("Select failed")); - return ResultState::TRANSIENT_ERROR; - } - - if (Res == 0) - { - _error->Error(_("Connection timed out")); - return Die(Req); - } - - // Handle server IO - if (ServerFd->Fd() != -1 && FD_ISSET(ServerFd->Fd(), &rfds)) - { - errno = 0; - if (In.Read(ServerFd) == false) - return Die(Req); - } + if (rd == -1) { + CfrsError(uri.Host.c_str(), rs); + cached.insert(hs); + Fail(true); + goto done; + } - if (ServerFd->Fd() != -1 && FD_ISSET(ServerFd->Fd(), &wfds)) - { - errno = 0; - if (Out.Write(ServerFd) == false) - return Die(Req); - } + Res.Filename = Queue->DestFile; + + hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader); + sc = CFHTTPMessageGetResponseStatusCode(hm); + + if (sc == 301 || sc == 302) { + sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Location")); + if (sr == NULL) { + Fail(); + goto done_; + } else { + size_t ln = CFStringGetLength(sr) + 1; + free(url); + url = static_cast<char *>(malloc(ln)); + + if (!CFStringGetCString(sr, url, ln, se)) { + Fail(); + goto done_; + } + + CFRelease(sr); + goto url; + } + } - // Send data to the file - if (FileFD->Fd() != -1 && FD_ISSET(FileFD->Fd(), &wfds)) - { - if (In.Write(FileFD) == false) - { - _error->Errno("write", _("Error writing to output file")); - return ResultState::TRANSIENT_ERROR; + sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range")); + if (sr != NULL) { + size_t ln = CFStringGetLength(sr) + 1; + char cr[ln]; + + if (!CFStringGetCString(sr, cr, ln, se)) { + Fail(); + goto done_; + } + + CFRelease(sr); + + if (sscanf(cr, "bytes %lu-%*u/%llu", &offset, &Res.Size) != 2) { + _error->Error(_("The HTTP server sent an invalid Content-Range header")); + Fail(); + goto done_; + } + + if (offset > Res.Size) { + _error->Error(_("This HTTP server has broken range support")); + Fail(); + goto done_; + } + } else { + sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length")); + if (sr != NULL) { + Res.Size = CFStringGetIntValue(sr); + CFRelease(sr); + } } - } - if (Req.MaximumSize > 0 && Req.File.IsOpen() && Req.File.Failed() == false && Req.File.Tell() > Req.MaximumSize) - { - Owner->SetFailReason("MaximumSizeExceeded"); - _error->Error(_("File has unexpected size (%llu != %llu). Mirror sync in progress?"), - Req.File.Tell(), Req.MaximumSize); - return ResultState::FATAL_ERROR; - } + time(&Res.LastModified); - // Handle commands from APT - if (FD_ISSET(STDIN_FILENO,&rfds)) - { - if (Owner->Run(true) != -1) - exit(100); - } + sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Last-Modified")); + if (sr != NULL) { + size_t ln = CFStringGetLength(sr) + 1; + char cr[ln]; - return ResultState::SUCCESSFUL; -} - /*}}}*/ + if (!CFStringGetCString(sr, cr, ln, se)) { + Fail(); + goto done_; + } -// HttpMethod::SendReq - Send the HTTP request /*{{{*/ -// --------------------------------------------------------------------- -/* This places the http request in the outbound buffer */ -void HttpMethod::SendReq(FetchItem *Itm) -{ - URI Uri = Itm->Uri; - { - auto const plus = Binary.find('+'); - if (plus != std::string::npos) - Uri.Access = Binary.substr(plus + 1); - } + CFRelease(sr); - // The HTTP server expects a hostname with a trailing :port - std::stringstream Req; - string ProperHost; - - if (Uri.Host.find(':') != string::npos) - ProperHost = '[' + Uri.Host + ']'; - else - ProperHost = Uri.Host; - - /* RFC 2616 ยง5.1.2 requires absolute URIs for requests to proxies, - but while its a must for all servers to accept absolute URIs, - it is assumed clients will sent an absolute path for non-proxies */ - std::string requesturi; - if ((Server->Proxy.Access != "http" && Server->Proxy.Access != "https") || APT::String::Endswith(Uri.Access, "https") || Server->Proxy.empty() == true || Server->Proxy.Host.empty()) - requesturi = Uri.Path; - else - requesturi = Uri; - - // The "+" is encoded as a workaround for a amazon S3 bug - // see LP bugs #1003633 and #1086997. - requesturi = QuoteString(requesturi, "+~ "); - - /* Build the request. No keep-alive is included as it is the default - in 1.1, can cause problems with proxies, and we are an HTTP/1.1 - client anyway. - C.f. https://tools.ietf.org/wg/httpbis/trac/ticket/158 */ - Req << "GET " << requesturi << " HTTP/1.1\r\n"; - if (Uri.Port != 0) - Req << "Host: " << ProperHost << ":" << std::to_string(Uri.Port) << "\r\n"; - else - Req << "Host: " << ProperHost << "\r\n"; - - // generate a cache control header (if needed) - if (ConfigFindB("No-Cache",false) == true) - Req << "Cache-Control: no-cache\r\n" - << "Pragma: no-cache\r\n"; - else if (Itm->IndexFile == true) - Req << "Cache-Control: max-age=" << std::to_string(ConfigFindI("Max-Age", 0)) << "\r\n"; - else if (ConfigFindB("No-Store", false) == true) - Req << "Cache-Control: no-store\r\n"; - - // If we ask for uncompressed files servers might respond with content- - // negotiation 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 (ConfigFindB("SendAccept", true) == true) - { - size_t const filepos = Itm->Uri.find_last_of('/'); - string const file = Itm->Uri.substr(filepos + 1); - if (flExtension(file) == file) - Req << "Accept: text/*\r\n"; - } + if (!RFC1123StrToTime(cr, Res.LastModified)) { + _error->Error(_("Unknown date format")); + Fail(); + goto done_; + } + } - // Check for a partial file and send if-queries accordingly - struct stat SBuf; - if (Server->RangesAllowed && stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) - Req << "Range: bytes=" << std::to_string(SBuf.st_size) << "-\r\n" - << "If-Range: " << TimeRFC1123(SBuf.st_mtime, false) << "\r\n"; - else if (Itm->LastModified != 0) - Req << "If-Modified-Since: " << TimeRFC1123(Itm->LastModified, false).c_str() << "\r\n"; + if (sc < 200 || (sc >= 300 && sc != 304)) { + sr = CFHTTPMessageCopyResponseStatusLine(hm); - if ((Server->Proxy.Access == "http" || Server->Proxy.Access == "https") && - (Server->Proxy.User.empty() == false || Server->Proxy.Password.empty() == false)) - Req << "Proxy-Authorization: Basic " - << Base64Encode(Server->Proxy.User + ":" + Server->Proxy.Password) << "\r\n"; + size_t ln = CFStringGetLength(sr) + 1; + char cr[ln]; - MaybeAddAuthTo(Uri); - if (Uri.User.empty() == false || Uri.Password.empty() == false) - Req << "Authorization: Basic " - << Base64Encode(Uri.User + ":" + Uri.Password) << "\r\n"; + if (!CFStringGetCString(sr, cr, ln, se)) { + Fail(); + goto done; + } - Req << "User-Agent: " << ConfigFind("User-Agent", - "Debian APT-HTTP/1.3 (" PACKAGE_VERSION ")") << "\r\n"; + CFRelease(sr); - auto const referer = ConfigFind("Referer", ""); - if (referer.empty() == false) - Req << "Referer: " << referer << "\r\n"; + _error->Error("%s", cr); - Req << "\r\n"; + Fail(); + goto done_; + } - if (Debug == true) - cerr << Req.str() << endl; + CFRelease(hm); + + if (sc == 304) { + unlink(Queue->DestFile.c_str()); + Res.IMSHit = true; + Res.LastModified = Queue->LastModified; + URIDone(Res); + } else { + Hashes hash; + + File = new FileFd(Queue->DestFile, FileFd::WriteAny); + if (_error->PendingError() == true) { + delete File; + File = NULL; + Fail(); + goto done; + } + + FailFile = Queue->DestFile; + FailFile.c_str(); // Make sure we dont do a malloc in the signal handler + FailFd = File->Fd(); + FailTime = Res.LastModified; + + Res.ResumePoint = offset; + ftruncate(File->Fd(), offset); + + if (offset != 0) { + lseek(File->Fd(), 0, SEEK_SET); + if (!hash.AddFD(File->Fd(), offset)) { + _error->Errno("read", _("Problem hashing file")); + delete File; + File = NULL; + Fail(); + goto done; + } + } + + lseek(File->Fd(), 0, SEEK_END); + + URIStart(Res); + + read: if (rd == -1) { + CfrsError("rd", rs); + Fail(true); + } else if (rd == 0) { + if (Res.Size == 0) + Res.Size = File->Size(); + + // Timestamp + struct timeval times[2]; + times[0].tv_sec = times[1].tv_sec = Res.LastModified; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(Queue->DestFile.c_str(), times); + + Res.TakeHashes(hash); + URIDone(Res); + } else { + hash.Add(data, rd); + + uint8_t *dt = data; + while (rd != 0) { + int sz = write(File->Fd(), dt, rd); + + if (sz == -1) { + delete File; + File = NULL; + Fail(); + goto done; + } + + dt += sz; + rd -= sz; + } + + rd = CFReadStreamRead(rs, data, sizeof(data)); + goto read; + } + } - Server->WriteResponse(Req.str()); -} - /*}}}*/ -std::unique_ptr<ServerState> HttpMethod::CreateServerState(URI const &uri)/*{{{*/ -{ - return std::unique_ptr<ServerState>(new HttpServerState(uri, this)); -} - /*}}}*/ -void HttpMethod::RotateDNS() /*{{{*/ -{ - ::RotateDNS(); -} - /*}}}*/ -BaseHttpMethod::DealWithHeadersResult HttpMethod::DealWithHeaders(FetchResult &Res, RequestState &Req)/*{{{*/ -{ - auto ret = BaseHttpMethod::DealWithHeaders(Res, Req); - if (ret != BaseHttpMethod::FILE_IS_OPEN) - return ret; - if (Req.File.Open(Queue->DestFile, FileFd::WriteAny) == false) - return ERROR_NOT_FROM_SERVER; + goto done; - FailFile = Queue->DestFile; - FailFile.c_str(); // Make sure we don't do a malloc in the signal handler - FailFd = Req.File.Fd(); - FailTime = Req.Date; + done_: + CFRelease(hm); + done: + CFReadStreamClose(rs); + CFRelease(rs); + free(url); - if (Server->InitHashes(Queue->ExpectedHashes) == false || Req.AddPartialFileToHashes(Req.File) == false) - { - _error->Errno("read",_("Problem hashing file")); - return ERROR_NOT_FROM_SERVER; + FailCounter = 0; } - if (Req.StartPos > 0) - Res.ResumePoint = Req.StartPos; - - SetNonBlock(Req.File.Fd(),true); - return FILE_IS_OPEN; + + return 0; } /*}}}*/ -HttpMethod::HttpMethod(std::string &&pProg) : BaseHttpMethod(std::move(pProg), "1.2", Pipeline | SendConfig) /*{{{*/ +HttpMethod::HttpMethod(std::string &&pProg) : BaseHttpMethod(pProg.c_str(), "1.2", Pipeline | SendConfig)/*{{{*/ { SeccompFlags = aptMethod::BASE | aptMethod::NETWORK; @@ -1036,6 +560,8 @@ HttpMethod::HttpMethod(std::string &&pProg) : BaseHttpMethod(std::move(pProg), " addName = std::move(name2); addName = Binary.substr(0, plus); } + File = 0; + Server = 0; } /*}}}*/ @@ -1044,6 +570,41 @@ int main(int, const char *argv[]) // ignore SIGPIPE, this can happen on write() if the socket // closes the connection (this is dealt with via ServerDie()) signal(SIGPIPE, SIG_IGN); + + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *machine = new char[size]; + sysctlbyname("hw.machine", machine, &size, NULL, 0); + Machine_ = machine; + + const char *path = "/System/Library/CoreServices/SystemVersion.plist"; + CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (uint8_t *) path, strlen(path), false); + + CFPropertyListRef plist; { + CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + CFReadStreamOpen(stream); + plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL); + CFReadStreamClose(stream); + } + + CFRelease(url); + + if (plist != NULL) { + Firmware_ = (CFStringRef) CFRetain(CFDictionaryGetValue((CFDictionaryRef) plist, CFSTR("ProductVersion"))); + CFRelease(plist); + } + + if (UniqueID_ == NULL) + if (void *libMobileGestalt = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY)) + if (CFStringRef (*$MGCopyAnswer)(CFStringRef) = (CFStringRef (*)(CFStringRef)) dlsym(libMobileGestalt, "MGCopyAnswer")) + UniqueID_ = $MGCopyAnswer(CFSTR("UniqueDeviceID")); + + if (UniqueID_ == NULL) + if (void *lockdown = lockdown_connect()) { + UniqueID_ = lockdown_copy_value(lockdown, NULL, kLockdownUniqueDeviceIDKey); + lockdown_disconnect(lockdown); + } + std::string Binary = flNotDir(argv[0]); if (Binary.find('+') == std::string::npos && Binary != "https" && Binary != "http") Binary.append("+http"); |