Reorderd error handling
[ntk/apt.git] / methods / ftp.cc
index 1068a8f..a7fa832 100644 (file)
@@ -1,6 +1,6 @@
 // -*- mode: cpp; mode: fold -*-
 // Description                                                         /*{{{*/
-// $Id: ftp.cc,v 1.1 1999/03/15 06:00:59 jgg Exp $
+// $Id: ftp.cc,v 1.17 1999/12/09 03:45:56 jgg Exp $
 /* ######################################################################
 
    HTTP Aquire Method - This is the FTP aquire method for APT.
@@ -9,7 +9,9 @@
    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                                                       /*{{{*/
@@ -17,7 +19,6 @@
 #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                                      /*{{{*/
 // ---------------------------------------------------------------------
@@ -46,7 +52,7 @@ bool Debug;
 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));
 }
                                                                        /*}}}*/
@@ -76,9 +82,7 @@ void FTPConn::Close()
 // ---------------------------------------------------------------------
 /* 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)
@@ -105,14 +109,12 @@ bool FTPConn::Open()
       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
@@ -121,50 +123,15 @@ bool FTPConn::Open()
         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();
 }
                                                                        /*}}}*/
@@ -209,17 +176,10 @@ bool FTPConn::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
    {      
@@ -244,7 +204,10 @@ bool FTPConn::Login()
         
         // 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);
@@ -262,19 +225,14 @@ bool FTPConn::Login()
       
       // 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);
       }            
    }
 
@@ -292,6 +250,9 @@ bool FTPConn::Login()
 /* 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))
    {
@@ -315,12 +276,19 @@ bool FTPConn::ReadLine(string &Text)
 
       // 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)
-        return _error->Errno("read","Read error");
+      {
+        _error->Errno("read","Read error");
+        Close();
+        return false;
+      }      
       Len += Res;
    }
 
@@ -348,7 +316,7 @@ bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
    if (*End == ' ')
    {
       if (Debug == true)
-        cout << "<- '" << QuoteString(Text,"") << "'" << endl;
+        cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
       return true;
    }
    
@@ -390,7 +358,7 @@ bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
    }      
 
    if (Debug == true && _error->PendingError() == false)
-      cout << "<- '" << QuoteString(Text,"") << "'" << endl;
+      cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
       
    return !_error->PendingError();
 }
@@ -409,7 +377,7 @@ bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
    strcat(S,"\r\n");
  
    if (Debug == true)
-      cout << "-> '" << QuoteString(S,"") << "'" << endl;
+      cerr << "-> '" << QuoteString(S,"") << "'" << endl;
 
    // Send it off
    unsigned long Len = strlen(S);
@@ -417,11 +385,19 @@ bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
    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)
-        return _error->Errno("write","Write Error");
+      {
+        _error->Errno("write","Write Error");
+        Close();
+        return false;
+      }
+      
       Len -= Res;
       Start += Res;
    }
@@ -488,19 +464,20 @@ bool FTPConn::GoPasv()
 // 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         /*{{{*/
@@ -569,7 +546,7 @@ bool FTPConn::CreateDataFd()
         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;
@@ -583,24 +560,23 @@ bool FTPConn::CreateDataFd()
    }
    
    // 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)
@@ -652,6 +628,9 @@ bool FTPConn::Finalize()
    if (DataFd < 0)
       return _error->Errno("accept","Unable to accept connection");
 
+   close(DataListenFd);
+   DataListenFd = -1;
+   
    return true;
 }
                                                                        /*}}}*/
@@ -659,13 +638,15 @@ bool FTPConn::Finalize()
 // ---------------------------------------------------------------------
 /* 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)
@@ -676,13 +657,29 @@ bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume)
    
    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)
@@ -694,8 +691,11 @@ bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume)
    {
       // 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)
@@ -706,9 +706,13 @@ bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume)
            continue;
         break;
       }
-      
+   
+      MD5.Add(Buffer,Res);
       if (To.Write(Buffer,Res) == false)
+      {
+        Close();
         return false;
+      }      
    }
 
    // All done
@@ -724,37 +728,187 @@ bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume)
 }
                                                                        /*}}}*/
 
-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();
 }