// -*- mode: cpp; mode: fold -*-
// Description /*{{{*/
-// $Id: ftp.cc,v 1.1 1999/03/15 06:00:59 jgg Exp $
+// $Id: ftp.cc,v 1.15 1999/09/05 05:41:41 jgg Exp $
/* ######################################################################
HTTP Aquire Method - This is the FTP aquire method for APT.
at all. Commands are sent syncronously with the FTP server (as the
rfc recommends, but it is not really necessary..) and no tricks are
done to speed things along.
-
+
+ RFC 2428 describes the IPv6 FTP behavior
+
##################################################################### */
/*}}}*/
// Include Files /*{{{*/
#include <apt-pkg/acquire-method.h>
#include <apt-pkg/error.h>
#include <apt-pkg/md5.h>
-#include "ftp.h"
#include <sys/stat.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include "rfc2553emu.h"
+#include "connect.h"
+#include "ftp.h"
/*}}}*/
unsigned long TimeOut = 120;
URI Proxy;
-bool Debug;
+string FtpMethod::FailFile;
+int FtpMethod::FailFd = -1;
+time_t FtpMethod::FailTime = 0;
// FTPConn::FTPConn - Constructor /*{{{*/
// ---------------------------------------------------------------------
FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
DataListenFd(-1), ServerName(Srv)
{
- Debug = true;
+ Debug = _config->FindB("Debug::Acquire::Ftp",false);
memset(&PasvAddr,0,sizeof(PasvAddr));
}
/*}}}*/
// ---------------------------------------------------------------------
/* Connect to the server using a non-blocking connection and perform a
login. */
-string LastHost;
-in_addr LastHostA;
-bool FTPConn::Open()
+bool FTPConn::Open(pkgAcqMethod *Owner)
{
// Use the already open connection if possible.
if (ServerFd != -1)
Proxy = getenv("ftp_proxy");
// Determine what host and port to use based on the proxy settings
- int Port = 21;
+ int Port = 0;
string Host;
if (Proxy.empty() == true)
{
if (ServerName.Port != 0)
Port = ServerName.Port;
- else
- ServerName.Port = Port;
Host = ServerName.Host;
}
else
Port = Proxy.Port;
Host = Proxy.Host;
}
-
- /* We used a cached address record.. Yes this is against the spec but
- the way we have setup our rotating dns suggests that this is more
- sensible */
- if (LastHost != Host)
- {
-// Owner->Status("Connecting to %s",Host.c_str());
-
- // Lookup the host
- hostent *Addr = gethostbyname(Host.c_str());
- if (Addr == 0 || Addr->h_addr_list[0] == 0)
- return _error->Error("Could not resolve '%s'",Host.c_str());
- LastHost = Host;
- LastHostA = *(in_addr *)(Addr->h_addr_list[0]);
- }
-
-// Owner->Status("Connecting to %s (%s)",Host.c_str(),inet_ntoa(LastHostA));
-
- // Get a socket
- if ((ServerFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
- return _error->Errno("socket","Could not create a socket");
-
- // Connect to the server
- struct sockaddr_in server;
- server.sin_family = AF_INET;
- server.sin_port = htons(Port);
- server.sin_addr = LastHostA;
- SetNonBlock(ServerFd,true);
- if (connect(ServerFd,(sockaddr *)&server,sizeof(server)) < 0 &&
- errno != EINPROGRESS)
- return _error->Errno("socket","Could not create a socket");
- Peer = server;
-
- /* This implements a timeout for connect by opening the connection
- nonblocking */
- if (WaitFd(ServerFd,true,TimeOut) == false)
- return _error->Error("Could not connect, connection timed out");
- unsigned int Err;
- unsigned int Len = sizeof(Err);
- if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
- return _error->Errno("getsockopt","Failed");
- if (Err != 0)
- return _error->Error("Could not connect.");
+ // Connect to the remote server
+ if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
+ return false;
+ socklen_t Len = sizeof(Peer);
+ if (getpeername(ServerFd,(sockaddr *)&Peer,&Len) != 0)
+ return _error->Errno("getpeername","Unable to determine the peer name");
+
+ Owner->Status("Logging in");
return Login();
}
/*}}}*/
return _error->Error("PASS failed, server said: %s",Msg.c_str());
// Enter passive mode
- TryPassive = false;
- if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true &&
- _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true) == true)
- {
- TryPassive = true;
- }
+ if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
+ TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
else
- {
- if (_config->FindB("Acquire::FTP::Passive",true) == true)
- TryPassive = true;
- }
+ TryPassive = _config->FindB("Acquire::FTP::Passive",true);
}
else
{
// Substitute the variables into the command
char SitePort[20];
- sprintf(SitePort,"%u",ServerName.Port);
+ if (ServerName.Port != 0)
+ sprintf(SitePort,"%u",ServerName.Port);
+ else
+ strcpy(SitePort,"21");
string Tmp = Opts->Value;
Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
// Enter passive mode
TryPassive = false;
- if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true &&
- _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true) == true)
- {
- TryPassive = true;
- }
+ if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
+ TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
else
{
- if (_config->Exists("Acquire::FTP::Proxy::Passive") == true &&
- _config->FindB("Acquire::FTP::Proxy::Passive",true) == true)
- TryPassive = true;
+ if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
+ TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
else
- if (_config->FindB("Acquire::FTP::Passive",true) == true)
- TryPassive = true;
+ TryPassive = _config->FindB("Acquire::FTP::Passive",true);
}
}
/* This performs a very simple buffered read. */
bool FTPConn::ReadLine(string &Text)
{
+ if (ServerFd == -1)
+ return false;
+
// Suck in a line
while (Len < sizeof(Buffer))
{
// Wait for some data..
if (WaitFd(ServerFd,false,TimeOut) == false)
+ {
+ Close();
return _error->Error("Connection timeout");
+ }
// Suck it back
- int Res = read(ServerFd,Buffer,sizeof(Buffer) - Len);
+ int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
if (Res <= 0)
+ {
+ Close();
return _error->Errno("read","Read error");
+ }
Len += Res;
}
if (*End == ' ')
{
if (Debug == true)
- cout << "<- '" << QuoteString(Text,"") << "'" << endl;
+ cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
return true;
}
}
if (Debug == true && _error->PendingError() == false)
- cout << "<- '" << QuoteString(Text,"") << "'" << endl;
+ cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
return !_error->PendingError();
}
strcat(S,"\r\n");
if (Debug == true)
- cout << "-> '" << QuoteString(S,"") << "'" << endl;
+ cerr << "-> '" << QuoteString(S,"") << "'" << endl;
// Send it off
unsigned long Len = strlen(S);
while (Len != 0)
{
if (WaitFd(ServerFd,true,TimeOut) == false)
+ {
+ Close();
return _error->Error("Connection timeout");
+ }
int Res = write(ServerFd,S + Start,Len);
if (Res <= 0)
+ {
+ Close();
return _error->Errno("write","Write Error");
+ }
+
Len -= Res;
Start += Res;
}
// FTPConn::Size - Return the size of a file /*{{{*/
// ---------------------------------------------------------------------
/* Grab the file size from the server, 0 means no size or empty file */
-unsigned long FTPConn::Size(const char *Path)
+bool FTPConn::Size(const char *Path,unsigned long &Size)
{
// Query the size
unsigned int Tag;
string Msg;
+ Size = 0;
if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
return false;
char *End;
- unsigned long Size = strtol(Msg.c_str(),&End,10);
+ Size = strtol(Msg.c_str(),&End,10);
if (Tag >= 400 || End == Msg.c_str())
- return 0;
- return Size;
+ Size = 0;
+ return true;
}
/*}}}*/
// FTPConn::ModTime - Return the modification time of the file /*{{{*/
return _error->Errno("socket","Could not create a socket");
/* This implements a timeout for connect by opening the connection
- nonblocking */
+ nonblocking */
if (WaitFd(ServerFd,true,TimeOut) == false)
return _error->Error("Could not connect data socket, connection timed out");
unsigned int Err;
}
// Port mode :<
- if (DataListenFd == -1)
- {
- // Get a socket
- if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
- return _error->Errno("socket","Could not create a socket");
-
- // Bind and listen
- sockaddr_in Addr;
- memset(&Addr,0,sizeof(Addr));
- if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
- return _error->Errno("bind","Could not bind a socket");
- if (listen(DataListenFd,1) < 0)
- return _error->Errno("listen","Could not listen on the socket");
- SetNonBlock(DataListenFd,true);
- }
+ close(DataListenFd);
+ DataListenFd = -1;
+
+ // Get a socket
+ if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
+ return _error->Errno("socket","Could not create a socket");
- // Determine the name to send to the remote
+ // Bind and listen
sockaddr_in Addr;
+ memset(&Addr,0,sizeof(Addr));
+ if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
+ return _error->Errno("bind","Could not bind a socket");
+ if (listen(DataListenFd,1) < 0)
+ return _error->Errno("listen","Could not listen on the socket");
+ SetNonBlock(DataListenFd,true);
+
+ // Determine the name to send to the remote
sockaddr_in Addr2;
socklen_t Jnk = sizeof(Addr);
if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
if (DataFd < 0)
return _error->Errno("accept","Unable to accept connection");
+ close(DataListenFd);
+ DataListenFd = -1;
+
return true;
}
/*}}}*/
// ---------------------------------------------------------------------
/* This opens a data connection, sends REST and RETR and then
transfers the file over. */
-bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume)
+bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
+ MD5Summation &MD5,bool &Missing)
{
+ Missing = false;
if (CreateDataFd() == false)
return false;
unsigned int Tag;
- string Msg;
+ string Msg;
if (Resume != 0)
{
if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
if (To.Truncate(Resume) == false)
return false;
+
+ if (To.Seek(0) == false)
+ return false;
+
+ if (Resume != 0)
+ {
+ if (MD5.AddFD(To.Fd(),Resume) == false)
+ {
+ _error->Errno("read","Problem hashing file");
+ return false;
+ }
+ }
// Send the get command
if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
return false;
if (Tag >= 400)
+ {
+ if (Tag == 550)
+ Missing = true;
return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
+ }
// Finish off the data connection
if (Finalize() == false)
{
// Wait for some data..
if (WaitFd(DataFd,false,TimeOut) == false)
- return _error->Error("Data socket connect timed out");
-
+ {
+ Close();
+ return _error->Error("Data socket timed out");
+ }
+
// Read the data..
int Res = read(DataFd,Buffer,sizeof(Buffer));
if (Res == 0)
continue;
break;
}
-
+
+ MD5.Add(Buffer,Res);
if (To.Write(Buffer,Res) == false)
+ {
+ Close();
return false;
+ }
}
// All done
}
/*}}}*/
-int main()
+// FtpMethod::FtpMethod - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
{
- FTPConn Con(URI("ftp://va.debian.org/debian/README"));
- string Msg;
- _config->Set("Acquire::FTP::Passive","false");
+ signal(SIGTERM,SigTerm);
+ signal(SIGINT,SigTerm);
- while (1)
+ Server = 0;
+ FailFd = -1;
+}
+ /*}}}*/
+// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
+// ---------------------------------------------------------------------
+/* This closes and timestamps the open file. This is neccessary to get
+ resume behavoir on user abort */
+void FtpMethod::SigTerm(int)
+{
+ if (FailFd == -1)
+ exit(100);
+ close(FailFd);
+
+ // Timestamp
+ struct utimbuf UBuf;
+ UBuf.actime = FailTime;
+ UBuf.modtime = FailTime;
+ utime(FailFile.c_str(),&UBuf);
+
+ exit(100);
+}
+ /*}}}*/
+// FtpMethod::Configuration - Handle a configuration message /*{{{*/
+// ---------------------------------------------------------------------
+/* We stash the desired pipeline depth */
+bool FtpMethod::Configuration(string Message)
+{
+ if (pkgAcqMethod::Configuration(Message) == false)
+ return false;
+
+ TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
+ return true;
+}
+ /*}}}*/
+// FtpMethod::Fetch - Fetch a file /*{{{*/
+// ---------------------------------------------------------------------
+/* Fetch a single file, called by the base class.. */
+bool FtpMethod::Fetch(FetchItem *Itm)
+{
+ URI Get = Itm->Uri;
+ const char *File = Get.Path.c_str();
+ FetchResult Res;
+ Res.Filename = Itm->DestFile;
+ Res.IMSHit = false;
+
+ // Connect to the server
+ if (Server == 0 || Server->Comp(Get) == false)
{
- if (Con.Open() == false)
- break;
- cout << "Size: " << Con.Size("/debian/README") << endl;
-
- time_t Time;
- Con.ModTime("/debian/README",Time);
- cout << "Time: " << TimeRFC1123(Time) << endl;
+ delete Server;
+ Server = new FTPConn(Get);
+ }
+
+ // Could not connect is a transient error..
+ if (Server->Open(this) == false)
+ {
+ Server->Close();
+ Fail(true);
+ return true;
+ }
+
+ // Get the files information
+ Status("Query");
+ unsigned long Size;
+ if (Server->Size(File,Size) == false ||
+ Server->ModTime(File,FailTime) == false)
+ {
+ Fail(true);
+ return true;
+ }
+ Res.Size = Size;
+
+ // See if it is an IMS hit
+ if (Itm->LastModified == FailTime)
+ {
+ Res.Size = 0;
+ Res.IMSHit = true;
+ URIDone(Res);
+ return true;
+ }
+ // See if the file exists
+ struct stat Buf;
+ if (stat(Itm->DestFile.c_str(),&Buf) == 0)
+ {
+ if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
{
-
- FileFd F("t",FileFd::WriteEmpty);
- Con.Get("/debian/README",F);
+ Res.Size = Buf.st_size;
+ Res.LastModified = Buf.st_mtime;
+ URIDone(Res);
+ return true;
}
+ // Resume?
+ if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
+ Res.ResumePoint = Buf.st_size;
+ }
+
+ // Open the file
+ MD5Summation MD5;
+ {
+ FileFd Fd(Itm->DestFile,FileFd::WriteAny);
+ if (_error->PendingError() == true)
+ return false;
+
+ URIStart(Res);
+
+ FailFile = Itm->DestFile;
+ FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
+ FailFd = Fd.Fd();
+
+ bool Missing;
+ if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
{
+ Fd.Close();
- FileFd F("t3",FileFd::WriteEmpty);
- Con.Get("/debian/README.pgp",F);
+ // Timestamp
+ struct utimbuf UBuf;
+ time(&UBuf.actime);
+ UBuf.actime = FailTime;
+ UBuf.modtime = FailTime;
+ utime(FailFile.c_str(),&UBuf);
+
+ // If the file is missing we hard fail otherwise transient fail
+ if (Missing == true)
+ return false;
+ Fail(true);
+ return true;
}
-
- break;
+
+ Res.Size = Fd.Size();
}
- _error->DumpErrors();
- return 0;
+ Res.LastModified = FailTime;
+ Res.MD5Sum = MD5.Result();
+
+ // Timestamp
+ struct utimbuf UBuf;
+ time(&UBuf.actime);
+ UBuf.actime = FailTime;
+ UBuf.modtime = FailTime;
+ utime(Queue->DestFile.c_str(),&UBuf);
+ FailFd = -1;
+
+ URIDone(Res);
+
+ return true;
+}
+ /*}}}*/
+
+int main(int argc,const char *argv[])
+{
+ /* See if we should be come the http client - we do this for http
+ proxy urls */
+ if (getenv("ftp_proxy") != 0)
+ {
+ URI Proxy = string(getenv("ftp_proxy"));
+ if (Proxy.Access == "http")
+ {
+ // Copy over the environment setting
+ char S[300];
+ snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
+ putenv(S);
+
+ // Run the http method
+ string Path = flNotFile(argv[0]) + "/http";
+ execl(Path.c_str(),Path.c_str(),0);
+ cerr << "Unable to invoke " << Path << endl;
+ exit(100);
+ }
+ }
+
+ FtpMethod Mth;
+
+ return Mth.Run();
}