// -*- mode: cpp; mode: fold -*-
// Description /*{{{*/
-// $Id: acquire-item.cc,v 1.46 2003/02/02 22:19:17 jgg Exp $
+// $Id: acquire-item.cc,v 1.46.2.9 2004/01/16 18:51:11 mdz Exp $
/* ######################################################################
Acquire Item - Item to acquire
#include <apt-pkg/acquire-item.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/sourcelist.h>
+#include <apt-pkg/vendorlist.h>
#include <apt-pkg/error.h>
#include <apt-pkg/strutl.h>
#include <apt-pkg/fileutl.h>
+#include <apt-pkg/md5.h>
#include <apti18n.h>
#include <stdio.h>
/*}}}*/
-using std::string;
+using namespace std;
// Acquire::Item::Item - Constructor /*{{{*/
// ---------------------------------------------------------------------
/* The package file is added to the queue and a second class is
instantiated to fetch the revision file */
pkgAcqIndex::pkgAcqIndex(pkgAcquire *Owner,
- string URI,string URIDesc,string ShortDesc) :
- Item(Owner), RealURI(URI)
+ string URI,string URIDesc,string ShortDesc,
+ string ExpectedMD5, string comprExt) :
+ Item(Owner), RealURI(URI), ExpectedMD5(ExpectedMD5)
{
Decompression = false;
Erase = false;
-
+
DestFile = _config->FindDir("Dir::State::lists") + "partial/";
DestFile += URItoFileName(URI);
- // Create the item
- if(FileExists("/usr/bin/bzip2"))
- Desc.URI = URI + ".bz2";
- else
- Desc.URI = URI + ".gz";
+ if(comprExt.empty())
+ {
+ // autoselect the compression method
+ if(FileExists("/bin/bzip2"))
+ CompressionExtension = ".bz2";
+ else
+ CompressionExtension = ".gz";
+ } else {
+ CompressionExtension = comprExt;
+ }
+ Desc.URI = URI + CompressionExtension;
+
Desc.Description = URIDesc;
Desc.Owner = this;
Desc.ShortDesc = ShortDesc;
void pkgAcqIndex::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
{
// no .bz2 found, retry with .gz
- if(Desc.URI.substr(Desc.URI.size()-3,Desc.URI.size()-1) == "bz2") {
+ if(Desc.URI.substr(Desc.URI.size()-3) == "bz2") {
Desc.URI = Desc.URI.substr(0,Desc.URI.size()-3) + "gz";
- QueueURI(Desc);
+
+ // retry with a gzip one
+ new pkgAcqIndex(Owner, RealURI, Desc.Description,Desc.ShortDesc,
+ ExpectedMD5, string(".gz"));
+ Status = StatDone;
+ Complete = false;
+ Dequeue();
return;
}
if (Decompression == true)
{
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ {
+ std::cerr << std::endl << RealURI << ": Computed MD5: " << MD5;
+ std::cerr << " Expected MD5: " << ExpectedMD5 << std::endl;
+ }
+
+ if (MD5.empty())
+ {
+ MD5Summation sum;
+ FileFd Fd(DestFile, FileFd::ReadOnly);
+ sum.AddFD(Fd.Fd(), Fd.Size());
+ Fd.Close();
+ MD5 = (string)sum.Result();
+ }
+
+ if (!ExpectedMD5.empty() && MD5 != ExpectedMD5)
+ {
+ Status = StatAuthError;
+ ErrorText = _("MD5Sum mismatch");
+ Rename(DestFile,DestFile + ".FAILED");
+ return;
+ }
// Done, move it into position
string FinalFile = _config->FindDir("Dir::State::lists");
FinalFile += URItoFileName(RealURI);
// The files timestamp matches
if (StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false) == true)
return;
-
+
Decompression = true;
Local = true;
DestFile += ".decomp";
else
Local = true;
- string compExt = Desc.URI.substr(Desc.URI.size()-3,Desc.URI.size()-1);
+ string compExt = Desc.URI.substr(Desc.URI.size()-3);
char *decompProg;
if(compExt == "bz2")
decompProg = "bzip2";
QueueURI(Desc);
Mode = decompProg;
}
- /*}}}*/
-// AcqIndexRel::pkgAcqIndexRel - Constructor /*{{{*/
-// ---------------------------------------------------------------------
-/* The Release file is added to the queue */
-pkgAcqIndexRel::pkgAcqIndexRel(pkgAcquire *Owner,
- string URI,string URIDesc,string ShortDesc) :
- Item(Owner), RealURI(URI)
+pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire *Owner,
+ string URI,string URIDesc,string ShortDesc,
+ string MetaIndexURI, string MetaIndexURIDesc,
+ string MetaIndexShortDesc,
+ const vector<IndexTarget*>* IndexTargets,
+ indexRecords* MetaIndexParser) :
+ Item(Owner), RealURI(URI), MetaIndexURI(MetaIndexURI),
+ MetaIndexURIDesc(MetaIndexURIDesc), MetaIndexShortDesc(MetaIndexShortDesc),
+ MetaIndexParser(MetaIndexParser), IndexTargets(IndexTargets)
{
DestFile = _config->FindDir("Dir::State::lists") + "partial/";
DestFile += URItoFileName(URI);
-
+
+ // remove any partial downloaded sig-file. it may confuse proxies
+ // and is too small to warrant a partial download anyway
+ unlink(DestFile.c_str());
+
// Create the item
- Desc.URI = URI;
Desc.Description = URIDesc;
- Desc.ShortDesc = ShortDesc;
Desc.Owner = this;
+ Desc.ShortDesc = ShortDesc;
+ Desc.URI = URI;
+
+
+ string Final = _config->FindDir("Dir::State::lists");
+ Final += URItoFileName(RealURI);
+ struct stat Buf;
+ if (stat(Final.c_str(),&Buf) == 0)
+ {
+ // File was already in place. It needs to be re-verified
+ // because Release might have changed, so Move it into partial
+ Rename(Final,DestFile);
+ }
QueueURI(Desc);
}
/*}}}*/
-// AcqIndexRel::Custom600Headers - Insert custom request headers /*{{{*/
+// pkgAcqMetaSig::Custom600Headers - Insert custom request headers /*{{{*/
// ---------------------------------------------------------------------
/* The only header we use is the last-modified header. */
-string pkgAcqIndexRel::Custom600Headers()
+string pkgAcqMetaSig::Custom600Headers()
{
- string Final = _config->FindDir("Dir::State::lists");
- Final += URItoFileName(RealURI);
-
struct stat Buf;
- if (stat(Final.c_str(),&Buf) != 0)
+ if (stat(DestFile.c_str(),&Buf) != 0)
return "\nIndex-File: true";
-
+
return "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
}
- /*}}}*/
-// AcqIndexRel::Done - Item downloaded OK /*{{{*/
-// ---------------------------------------------------------------------
-/* The release file was not placed into the download directory then
- a copy URI is generated and it is copied there otherwise the file
- in the partial directory is moved into .. and the URI is finished. */
-void pkgAcqIndexRel::Done(string Message,unsigned long Size,string MD5,
- pkgAcquire::MethodConfig *Cfg)
+
+void pkgAcqMetaSig::Done(string Message,unsigned long Size,string MD5,
+ pkgAcquire::MethodConfig *Cfg)
{
Item::Done(Message,Size,MD5,Cfg);
return;
}
- Complete = true;
-
- // The files timestamp matches
- if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
- return;
-
- // We have to copy it into place
if (FileName != DestFile)
{
+ // We have to copy it into place
Local = true;
Desc.URI = "copy:" + FileName;
QueueURI(Desc);
return;
}
-
- // Done, move it into position
- string FinalFile = _config->FindDir("Dir::State::lists");
- FinalFile += URItoFileName(RealURI);
- Rename(DestFile,FinalFile);
-
- chmod(FinalFile.c_str(),0644);
+
+ Complete = true;
+
+ // queue a pkgAcqMetaIndex to be verified against the sig we just retrieved
+ new pkgAcqMetaIndex(Owner, MetaIndexURI, MetaIndexURIDesc, MetaIndexShortDesc,
+ DestFile, IndexTargets, MetaIndexParser);
+
}
/*}}}*/
-// AcqIndexRel::Failed - Silence failure messages for missing rel files /*{{{*/
-// ---------------------------------------------------------------------
-/* */
-void pkgAcqIndexRel::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
+void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
{
+
+ // if we get a network error we fail gracefully
+ if(LookupTag(Message,"FailReason") == "Timeout" ||
+ LookupTag(Message,"FailReason") == "TmpResolveFailure" ||
+ LookupTag(Message,"FailReason") == "ConnectionRefused") {
+ Item::Failed(Message,Cnf);
+ return;
+ }
+
+ // Delete any existing sigfile when the acquire failed
+ string Final = _config->FindDir("Dir::State::lists") + URItoFileName(RealURI);
+ unlink(Final.c_str());
+
+ // queue a pkgAcqMetaIndex with no sigfile
+ new pkgAcqMetaIndex(Owner, MetaIndexURI, MetaIndexURIDesc, MetaIndexShortDesc,
+ "", IndexTargets, MetaIndexParser);
+
if (Cnf->LocalOnly == true ||
StringToBool(LookupTag(Message,"Transient-Failure"),false) == false)
{
Item::Failed(Message,Cnf);
}
+
+pkgAcqMetaIndex::pkgAcqMetaIndex(pkgAcquire *Owner,
+ string URI,string URIDesc,string ShortDesc,
+ string SigFile,
+ const vector<struct IndexTarget*>* IndexTargets,
+ indexRecords* MetaIndexParser) :
+ Item(Owner), RealURI(URI), SigFile(SigFile), AuthPass(false),
+ MetaIndexParser(MetaIndexParser), IndexTargets(IndexTargets), IMSHit(false)
+{
+ DestFile = _config->FindDir("Dir::State::lists") + "partial/";
+ DestFile += URItoFileName(URI);
+
+ // Create the item
+ Desc.Description = URIDesc;
+ Desc.Owner = this;
+ Desc.ShortDesc = ShortDesc;
+ Desc.URI = URI;
+
+ QueueURI(Desc);
+}
+
+ /*}}}*/
+// pkgAcqMetaIndex::Custom600Headers - Insert custom request headers /*{{{*/
+// ---------------------------------------------------------------------
+/* The only header we use is the last-modified header. */
+string pkgAcqMetaIndex::Custom600Headers()
+{
+ string Final = _config->FindDir("Dir::State::lists");
+ Final += URItoFileName(RealURI);
+
+ struct stat Buf;
+ if (stat(Final.c_str(),&Buf) != 0)
+ return "\nIndex-File: true";
+
+ return "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
+}
+
+void pkgAcqMetaIndex::Done(string Message,unsigned long Size,string MD5,
+ pkgAcquire::MethodConfig *Cfg)
+{
+ Item::Done(Message,Size,MD5,Cfg);
+
+ // MetaIndexes are done in two passes: one to download the
+ // metaindex with an appropriate method, and a second to verify it
+ // with the gpgv method
+
+ if (AuthPass == true)
+ {
+ AuthDone(Message);
+ }
+ else
+ {
+ RetrievalDone(Message);
+ if (!Complete)
+ // Still more retrieving to do
+ return;
+
+ if (SigFile == "")
+ {
+ // There was no signature file, so we are finished. Download
+ // the indexes without verification.
+ QueueIndexes(false);
+ }
+ else
+ {
+ // There was a signature file, so pass it to gpgv for
+ // verification
+
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ std::cerr << "Metaindex acquired, queueing gpg verification ("
+ << SigFile << "," << DestFile << ")\n";
+ AuthPass = true;
+ Desc.URI = "gpgv:" + SigFile;
+ QueueURI(Desc);
+ Mode = "gpgv";
+ }
+ }
+}
+
+void pkgAcqMetaIndex::RetrievalDone(string Message)
+{
+ // We have just finished downloading a Release file (it is not
+ // verified yet)
+
+ string FileName = LookupTag(Message,"Filename");
+ if (FileName.empty() == true)
+ {
+ Status = StatError;
+ ErrorText = "Method gave a blank filename";
+ return;
+ }
+
+ if (FileName != DestFile)
+ {
+ Local = true;
+ Desc.URI = "copy:" + FileName;
+ QueueURI(Desc);
+ return;
+ }
+
+ // see if the download was a IMSHit
+ IMSHit = StringToBool(LookupTag(Message,"IMS-Hit"),false);
+
+ Complete = true;
+
+ string FinalFile = _config->FindDir("Dir::State::lists");
+ FinalFile += URItoFileName(RealURI);
+
+ // The files timestamp matches
+ if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == false)
+ {
+ // Move it into position
+ Rename(DestFile,FinalFile);
+ }
+ DestFile = FinalFile;
+}
+
+void pkgAcqMetaIndex::AuthDone(string Message)
+{
+ // At this point, the gpgv method has succeeded, so there is a
+ // valid signature from a key in the trusted keyring. We
+ // perform additional verification of its contents, and use them
+ // to verify the indexes we are about to download
+
+ if (!MetaIndexParser->Load(DestFile))
+ {
+ Status = StatAuthError;
+ ErrorText = MetaIndexParser->ErrorText;
+ return;
+ }
+
+ if (!VerifyVendor(Message))
+ {
+ return;
+ }
+
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ std::cerr << "Signature verification succeeded: "
+ << DestFile << std::endl;
+
+ // Download further indexes with verification
+ QueueIndexes(true);
+
+ // Done, move signature file into position
+
+ string VerifiedSigFile = _config->FindDir("Dir::State::lists") +
+ URItoFileName(RealURI) + ".gpg";
+ Rename(SigFile,VerifiedSigFile);
+ chmod(VerifiedSigFile.c_str(),0644);
+}
+
+void pkgAcqMetaIndex::QueueIndexes(bool verify)
+{
+ for (vector <struct IndexTarget*>::const_iterator Target = IndexTargets->begin();
+ Target != IndexTargets->end();
+ Target++)
+ {
+ string ExpectedIndexMD5;
+ if (verify)
+ {
+ const indexRecords::checkSum *Record = MetaIndexParser->Lookup((*Target)->MetaKey);
+ if (!Record)
+ {
+ Status = StatAuthError;
+ ErrorText = "Unable to find expected entry "
+ + (*Target)->MetaKey + " in Meta-index file (malformed Release file?)";
+ return;
+ }
+ ExpectedIndexMD5 = Record->MD5Hash;
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ {
+ std::cerr << "Queueing: " << (*Target)->URI << std::endl;
+ std::cerr << "Expected MD5: " << ExpectedIndexMD5 << std::endl;
+ }
+ if (ExpectedIndexMD5.empty())
+ {
+ Status = StatAuthError;
+ ErrorText = "Unable to find MD5 sum for "
+ + (*Target)->MetaKey + " in Meta-index file";
+ return;
+ }
+ }
+
+ // Queue Packages file
+ new pkgAcqIndex(Owner, (*Target)->URI, (*Target)->Description,
+ (*Target)->ShortDesc, ExpectedIndexMD5);
+ }
+}
+
+bool pkgAcqMetaIndex::VerifyVendor(string Message)
+{
+// // Maybe this should be made available from above so we don't have
+// // to read and parse it every time?
+// pkgVendorList List;
+// List.ReadMainList();
+
+// const Vendor* Vndr = NULL;
+// for (std::vector<string>::const_iterator I = GPGVOutput.begin(); I != GPGVOutput.end(); I++)
+// {
+// string::size_type pos = (*I).find("VALIDSIG ");
+// if (_config->FindB("Debug::Vendor", false))
+// std::cerr << "Looking for VALIDSIG in \"" << (*I) << "\": pos " << pos
+// << std::endl;
+// if (pos != std::string::npos)
+// {
+// string Fingerprint = (*I).substr(pos+sizeof("VALIDSIG"));
+// if (_config->FindB("Debug::Vendor", false))
+// std::cerr << "Looking for \"" << Fingerprint << "\" in vendor..." <<
+// std::endl;
+// Vndr = List.FindVendor(Fingerprint) != "";
+// if (Vndr != NULL);
+// break;
+// }
+// }
+ string::size_type pos;
+
+ // check for missing sigs (that where not fatal because otherwise we had
+ // bombed earlier)
+ string missingkeys;
+ string msg = _("There are no public key available for the "
+ "following key IDs:\n");
+ pos = Message.find("NO_PUBKEY ");
+ if (pos != std::string::npos)
+ {
+ string::size_type start = pos+strlen("NO_PUBKEY ");
+ string Fingerprint = Message.substr(start, Message.find("\n")-start);
+ missingkeys += (Fingerprint);
+ }
+ if(!missingkeys.empty())
+ _error->Warning("%s", string(msg+missingkeys).c_str());
+
+ string Transformed = MetaIndexParser->GetExpectedDist();
+
+ if (Transformed == "../project/experimental")
+ {
+ Transformed = "experimental";
+ }
+
+ pos = Transformed.rfind('/');
+ if (pos != string::npos)
+ {
+ Transformed = Transformed.substr(0, pos);
+ }
+
+ if (Transformed == ".")
+ {
+ Transformed = "";
+ }
+
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ {
+ std::cerr << "Got Codename: " << MetaIndexParser->GetDist() << std::endl;
+ std::cerr << "Expecting Dist: " << MetaIndexParser->GetExpectedDist() << std::endl;
+ std::cerr << "Transformed Dist: " << Transformed << std::endl;
+ }
+
+ if (MetaIndexParser->CheckDist(Transformed) == false)
+ {
+ // This might become fatal one day
+// Status = StatAuthError;
+// ErrorText = "Conflicting distribution; expected "
+// + MetaIndexParser->GetExpectedDist() + " but got "
+// + MetaIndexParser->GetDist();
+// return false;
+ if (!Transformed.empty())
+ {
+ _error->Warning("Conflicting distribution: %s (expected %s but got %s)",
+ Desc.Description.c_str(),
+ Transformed.c_str(),
+ MetaIndexParser->GetDist().c_str());
+ }
+ }
+
+ return true;
+}
+ /*}}}*/
+// pkgAcqMetaIndex::Failed - no Release file present or no signature
+// file present /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void pkgAcqMetaIndex::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
+{
+ if (AuthPass == true)
+ {
+ // if we fail the authentication but got the file via a IMS-Hit
+ // this means that the file wasn't downloaded and that it might be
+ // just stale (server problem, proxy etc). we delete what we have
+ // queue it again without i-m-s
+ // alternatively we could just unlink the file and let the user try again
+ if (IMSHit)
+ {
+ Complete = false;
+ Local = false;
+ AuthPass = false;
+ unlink(DestFile.c_str());
+
+ DestFile = _config->FindDir("Dir::State::lists") + "partial/";
+ DestFile += URItoFileName(RealURI);
+ Desc.URI = RealURI;
+ QueueURI(Desc);
+ return;
+ }
+
+ // gpgv method failed
+ _error->Warning("GPG error: %s: %s",
+ Desc.Description.c_str(),
+ LookupTag(Message,"Message").c_str());
+
+ }
+
+ // No Release file was present, or verification failed, so fall
+ // back to queueing Packages files without verification
+ QueueIndexes(false);
+}
+
/*}}}*/
// AcqArchive::AcqArchive - Constructor /*{{{*/
pkgRecords *Recs,pkgCache::VerIterator const &Version,
string &StoreFilename) :
Item(Owner), Version(Version), Sources(Sources), Recs(Recs),
- StoreFilename(StoreFilename), Vf(Version.FileList())
+ StoreFilename(StoreFilename), Vf(Version.FileList()),
+ Trusted(false)
{
Retries = _config->FindI("Acquire::Retries",0);
QuoteString(Version.Arch(),"_:.") +
"." + flExtension(Parse.FileName());
}
-
+
+ // check if we have one trusted source for the package. if so, switch
+ // to "TrustedOnly" mode
+ for (pkgCache::VerFileIterator i = Version.FileList(); i.end() == false; i++)
+ {
+ pkgIndexFile *Index;
+ if (Sources->FindIndex(i.File(),Index) == false)
+ continue;
+ if (_config->FindB("Debug::pkgAcquire::Auth", false))
+ {
+ std::cerr << "Checking index: " << Index->Describe()
+ << "(Trusted=" << Index->IsTrusted() << ")\n";
+ }
+ if (Index->IsTrusted()) {
+ Trusted = true;
+ break;
+ }
+ }
+
+ // "allow-unauthenticated" restores apts old fetching behaviour
+ // that means that e.g. unauthenticated file:// uris are higher
+ // priority than authenticated http:// uris
+ if (_config->FindB("APT::Get::AllowUnauthenticated",false) == true)
+ Trusted = false;
+
// Select a source
if (QueueNext() == false && _error->PendingError() == false)
_error->Error(_("I wasn't able to locate file for the %s package. "
if (Sources->FindIndex(Vf.File(),Index) == false)
continue;
+ // only try to get a trusted package from another source if that source
+ // is also trusted
+ if(Trusted && !Index->IsTrusted())
+ continue;
+
// Grab the text package record
pkgRecords::Parser &Parse = Recs->Lookup(Vf);
if (_error->PendingError() == true)
"field for package %s."),
Version.ParentPkg().Name());
+ Desc.URI = Index->ArchiveURI(PkgFile);
+ Desc.Description = Index->ArchiveInfo(Version);
+ Desc.Owner = this;
+ Desc.ShortDesc = Version.ParentPkg().Name();
+
// See if we already have the file. (Legacy filenames)
FileSize = Version->Size;
string FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(PkgFile);
{
Status = StatError;
ErrorText = _("MD5Sum mismatch");
- Rename(DestFile,DestFile + ".FAILED");
+ if(FileExists(DestFile))
+ Rename(DestFile,DestFile + ".FAILED");
return;
}
}
}
}
/*}}}*/
+// AcqArchive::IsTrusted - Determine whether this archive comes from a
+// trusted source /*{{{*/
+// ---------------------------------------------------------------------
+bool pkgAcqArchive::IsTrusted()
+{
+ return Trusted;
+}
+
// AcqArchive::Finished - Fetching has finished, tidy up /*{{{*/
// ---------------------------------------------------------------------
/* */
// ---------------------------------------------------------------------
/* The file is added to the queue */
pkgAcqFile::pkgAcqFile(pkgAcquire *Owner,string URI,string MD5,
- unsigned long Size,string Dsc,string ShortDesc) :
+ unsigned long Size,string Dsc,string ShortDesc,
+ const string &DestDir, const string &DestFilename) :
Item(Owner), Md5Hash(MD5)
{
Retries = _config->FindI("Acquire::Retries",0);
- DestFile = flNotDir(URI);
-
+ if(!DestFilename.empty())
+ DestFile = DestFilename;
+ else if(!DestDir.empty())
+ DestFile = DestDir + "/" + flNotDir(URI);
+ else
+ DestFile = flNotDir(URI);
+
// Create the item
Desc.URI = URI;
Desc.Description = Dsc;
else
PartialSize = Buf.st_size;
}
-
+
QueueURI(Desc);
}
/*}}}*/