95afe544525e0bc4f0fe6a9d8ffc72e8b2cc5c54
[apt.git] / methods / ftp.cc
1 // -*- mode: c++; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: ftp.cc,v 1.31 2003/08/10 02:24:39 mdz Exp $
4 /* ######################################################################
5
6    FTP Aquire Method - This is the FTP aquire method for APT.
7
8    This is a very simple implementation that does not try to optimize
9    at all. Commands are sent syncronously with the FTP server (as the
10    rfc recommends, but it is not really necessary..) and no tricks are
11    done to speed things along.
12                         
13    RFC 2428 describes the IPv6 FTP behavior
14    
15    ##################################################################### */
16                                                                         /*}}}*/
17 // Include Files                                                        /*{{{*/
18 #include <apt-pkg/fileutl.h>
19 #include <apt-pkg/acquire-method.h>
20 #include <apt-pkg/error.h>
21 #include <apt-pkg/hashes.h>
22
23 #include <sys/stat.h>
24 #include <sys/time.h>
25 #include <utime.h>
26 #include <unistd.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <stdarg.h>
31 #include <iostream>
32
33 // Internet stuff
34 #include <netinet/in.h>
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
37 #include <netdb.h>
38
39 // CNC:2003-02-20 - Moved header to fix compilation error when
40 //                  --disable-nls is used.
41 #include <apti18n.h>
42
43 #include "rfc2553emu.h"
44 #include "connect.h"
45 #include "ftp.h"
46                                                                         /*}}}*/
47
48 using namespace std;
49
50 /* This table is for the EPRT and EPSV commands, it maps the OS address
51    family to the IETF address families */
52 struct AFMap
53 {
54    unsigned long Family;
55    unsigned long IETFFamily;
56 };
57
58 #ifndef AF_INET6
59 struct AFMap AFMap[] = {{AF_INET,1},{0,0}};
60 #else
61 struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{0,0}};
62 #endif
63
64 unsigned long TimeOut = 120;
65 URI Proxy;
66 string FtpMethod::FailFile;
67 int FtpMethod::FailFd = -1;
68 time_t FtpMethod::FailTime = 0;
69
70 // FTPConn::FTPConn - Constructor                                       /*{{{*/
71 // ---------------------------------------------------------------------
72 /* */
73 FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1), 
74                             DataListenFd(-1), ServerName(Srv)
75 {
76    Debug = _config->FindB("Debug::Acquire::Ftp",false);
77    PasvAddr = 0;
78 }
79                                                                         /*}}}*/
80 // FTPConn::~FTPConn - Destructor                                       /*{{{*/
81 // ---------------------------------------------------------------------
82 /* */
83 FTPConn::~FTPConn()
84 {
85    Close();
86 }
87                                                                         /*}}}*/
88 // FTPConn::Close - Close down the connection                           /*{{{*/
89 // ---------------------------------------------------------------------
90 /* Just tear down the socket and data socket */
91 void FTPConn::Close()
92 {
93    close(ServerFd);
94    ServerFd = -1;
95    close(DataFd);
96    DataFd = -1;
97    close(DataListenFd);
98    DataListenFd = -1;
99    
100    if (PasvAddr != 0)
101       freeaddrinfo(PasvAddr);
102    PasvAddr = 0;
103 }
104                                                                         /*}}}*/
105 // FTPConn::Open - Open a new connection                                /*{{{*/
106 // ---------------------------------------------------------------------
107 /* Connect to the server using a non-blocking connection and perform a 
108    login. */
109 bool FTPConn::Open(pkgAcqMethod *Owner)
110 {
111    // Use the already open connection if possible.
112    if (ServerFd != -1)
113       return true;
114    
115    Close();
116    
117    // Determine the proxy setting
118    if (getenv("ftp_proxy") == 0)
119    {
120       string DefProxy = _config->Find("Acquire::ftp::Proxy");
121       string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
122       if (SpecificProxy.empty() == false)
123       {
124          if (SpecificProxy == "DIRECT")
125             Proxy = "";
126          else
127             Proxy = SpecificProxy;
128       }   
129       else
130          Proxy = DefProxy;
131    }
132    else
133       Proxy = getenv("ftp_proxy");
134    
135    // Parse no_proxy, a , separated list of domains
136    if (getenv("no_proxy") != 0)
137    {
138       if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
139          Proxy = "";
140    }
141    
142    // Determine what host and port to use based on the proxy settings
143    int Port = 0;
144    string Host;   
145    if (Proxy.empty() == true)
146    {
147       if (ServerName.Port != 0)
148          Port = ServerName.Port;
149       Host = ServerName.Host;
150    }
151    else
152    {
153       if (Proxy.Port != 0)
154          Port = Proxy.Port;
155       Host = Proxy.Host;
156    }
157
158    /* Connect to the remote server. Since FTP is connection oriented we
159       want to make sure we get a new server every time we reconnect */
160    RotateDNS();
161    if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
162       return false;
163
164    // Login must be before getpeername otherwise dante won't work.
165    Owner->Status(_("Logging in"));
166    bool Res = Login();
167    
168    // Get the remote server's address
169    PeerAddrLen = sizeof(PeerAddr);
170    if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
171       return _error->Errno("getpeername",_("Unable to determine the peer name"));
172    
173    // Get the local machine's address
174    ServerAddrLen = sizeof(ServerAddr);
175    if (getsockname(ServerFd,(sockaddr *)&ServerAddr,&ServerAddrLen) != 0)
176       return _error->Errno("getsockname",_("Unable to determine the local name"));
177    
178    return Res;
179 }
180                                                                         /*}}}*/
181 // FTPConn::Login - Login to the remote server                          /*{{{*/
182 // ---------------------------------------------------------------------
183 /* This performs both normal login and proxy login using a simples script
184    stored in the config file. */
185 bool FTPConn::Login()
186 {
187    unsigned int Tag;
188    string Msg;
189    
190    // Setup the variables needed for authentication
191    string User = "anonymous";
192    // CNC:2003-06-16
193    string Pass = "apt_get_ftp_2.1@rpm.linux.user";
194
195    // Fill in the user/pass
196    if (ServerName.User.empty() == false)
197       User = ServerName.User;
198    if (ServerName.Password.empty() == false)
199       Pass = ServerName.Password;
200        
201    // Perform simple login
202    if (Proxy.empty() == true)
203    {
204       // Read the initial response
205       if (ReadResp(Tag,Msg) == false)
206          return false;
207       if (Tag >= 400)
208          return _error->Error(_("Server refused our connection and said: %s"),Msg.c_str());
209       
210       // Send the user
211       if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
212          return false;
213       if (Tag >= 400)
214          return _error->Error(_("USER failed, server said: %s"),Msg.c_str());
215       
216       if (Tag == 331) { // 331 User name okay, need password.
217          // Send the Password
218          if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
219             return false;
220          if (Tag >= 400)
221             return _error->Error(_("PASS failed, server said: %s"),Msg.c_str());
222       }
223       
224       // Enter passive mode
225       if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
226          TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
227       else
228          TryPassive = _config->FindB("Acquire::FTP::Passive",true);      
229    }
230    else
231    {      
232       // Read the initial response
233       if (ReadResp(Tag,Msg) == false)
234          return false;
235       if (Tag >= 400)
236          return _error->Error(_("Server refused our connection and said: %s"),Msg.c_str());
237       
238       // Perform proxy script execution
239       Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
240       if (Opts == 0 || Opts->Child == 0)
241          return _error->Error(_("A proxy server was specified but no login "
242                               "script, Acquire::ftp::ProxyLogin is empty."));
243       Opts = Opts->Child;
244
245       // Iterate over the entire login script
246       for (; Opts != 0; Opts = Opts->Next)
247       {
248          if (Opts->Value.empty() == true)
249             continue;
250          
251          // Substitute the variables into the command
252          char SitePort[20];
253          if (ServerName.Port != 0)
254             sprintf(SitePort,"%u",ServerName.Port);
255          else
256             strcpy(SitePort,"21");
257          string Tmp = Opts->Value;
258          Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
259          Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
260          Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
261          Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
262          Tmp = SubstVar(Tmp,"$(SITE_PORT)",SitePort);
263          Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
264
265          // Send the command
266          if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
267             return false;
268          if (Tag >= 400)
269             return _error->Error(_("Login script command '%s' failed, server said: %s"),Tmp.c_str(),Msg.c_str());        
270       }
271       
272       // Enter passive mode
273       TryPassive = false;
274       if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
275          TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
276       else
277       {
278          if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
279             TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
280          else
281             TryPassive = _config->FindB("Acquire::FTP::Passive",true);
282       }            
283    }
284
285    // Force the use of extended commands
286    if (_config->Exists("Acquire::FTP::ForceExtended::" + ServerName.Host) == true)
287       ForceExtended = _config->FindB("Acquire::FTP::ForceExtended::" + ServerName.Host,true);
288    else
289       ForceExtended = _config->FindB("Acquire::FTP::ForceExtended",false);
290    
291    // Binary mode
292    if (WriteMsg(Tag,Msg,"TYPE I") == false)
293       return false;
294    if (Tag >= 400)
295       return _error->Error(_("TYPE failed, server said: %s"),Msg.c_str());
296    
297    return true;
298 }
299                                                                         /*}}}*/
300 // FTPConn::ReadLine - Read a line from the server                      /*{{{*/
301 // ---------------------------------------------------------------------
302 /* This performs a very simple buffered read. */
303 bool FTPConn::ReadLine(string &Text)
304 {
305    if (ServerFd == -1)
306       return false;
307    
308    // Suck in a line
309    while (Len < sizeof(Buffer))
310    {
311       // Scan the buffer for a new line
312       for (unsigned int I = 0; I != Len; I++)
313       {
314          // Escape some special chars
315          if (Buffer[I] == 0)
316             Buffer[I] = '?';
317          
318          // End of line?
319          if (Buffer[I] != '\n')
320             continue;
321          
322          I++;
323          Text = string(Buffer,I);
324          memmove(Buffer,Buffer+I,Len - I);
325          Len -= I;       
326          return true;
327       }
328
329       // Wait for some data..
330       if (WaitFd(ServerFd,false,TimeOut) == false)
331       {
332          Close();
333          return _error->Error(_("Connection timeout"));
334       }
335       
336       // Suck it back
337       int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
338       if (Res == 0)
339          _error->Error(_("Server closed the connection"));
340       if (Res <= 0)
341       {
342          _error->Errno("read",_("Read error"));
343          Close();
344          return false;
345       }      
346       Len += Res;
347    }
348
349    return _error->Error(_("A response overflowed the buffer."));
350 }
351                                                                         /*}}}*/
352 // FTPConn::ReadResp - Read a full response from the server             /*{{{*/
353 // ---------------------------------------------------------------------
354 /* This reads a reply code from the server, it handles both p */
355 bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
356 {
357    // Grab the first line of the response
358    string Msg;
359    if (ReadLine(Msg) == false)
360        return false;
361    
362    // Get the ID code
363    char *End;   
364    Ret = strtol(Msg.c_str(),&End,10);
365    if (End - Msg.c_str() != 3)
366       return _error->Error(_("Protocol corruption"));
367
368    // All done ?
369    Text = Msg.c_str()+4;
370    if (*End == ' ')
371    {
372       if (Debug == true)
373          cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
374       return true;
375    }
376    
377    if (*End != '-')
378       return _error->Error(_("Protocol corruption"));
379    
380    /* Okay, here we do the continued message trick. This is foolish, but
381       proftpd follows the protocol as specified and wu-ftpd doesn't, so 
382       we filter. I wonder how many clients break if you use proftpd and
383       put a '- in the 3rd spot in the message? */
384    char Leader[4];
385    strncpy(Leader,Msg.c_str(),3);
386    Leader[3] = 0;
387    while (ReadLine(Msg) == true)
388    {
389       // Short, it must be using RFC continuation..
390       if (Msg.length() < 4)
391       {
392          Text += Msg;
393          continue;
394       }
395       
396       // Oops, finished
397       if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
398       {
399          Text += Msg.c_str()+4;
400          break;
401       }
402       
403       // This message has the wu-ftpd style reply code prefixed
404       if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
405       {
406          Text += Msg.c_str()+4;
407          continue;
408       }
409       
410       // Must be RFC style prefixing
411       Text += Msg;
412    }       
413
414    if (Debug == true && _error->PendingError() == false)
415       cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
416       
417    return !_error->PendingError();
418 }
419                                                                         /*}}}*/
420 // FTPConn::WriteMsg - Send a message to the server                     /*{{{*/
421 // ---------------------------------------------------------------------
422 /* Simple printf like function.. */
423 bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
424 {
425    va_list args;
426    va_start(args,Fmt);
427
428    // sprintf the description
429    char S[400];
430    vsnprintf(S,sizeof(S) - 4,Fmt,args);
431    strcat(S,"\r\n");
432  
433    if (Debug == true)
434       cerr << "-> '" << QuoteString(S,"") << "'" << endl;
435
436    // Send it off
437    unsigned long Len = strlen(S);
438    unsigned long Start = 0;
439    while (Len != 0)
440    {
441       if (WaitFd(ServerFd,true,TimeOut) == false)
442       {
443          Close();
444          return _error->Error(_("Connection timeout"));
445       }
446       
447       int Res = write(ServerFd,S + Start,Len);
448       if (Res <= 0)
449       {
450          _error->Errno("write",_("Write Error"));
451          Close();
452          return false;
453       }
454       
455       Len -= Res;
456       Start += Res;
457    }
458    
459    return ReadResp(Ret,Text);
460 }
461                                                                         /*}}}*/
462 // FTPConn::GoPasv - Enter Passive mode                                 /*{{{*/
463 // ---------------------------------------------------------------------
464 /* Try to enter passive mode, the return code does not indicate if passive
465    mode could or could not be established, only if there was a fatal error. 
466    We have to enter passive mode every time we make a data connection :| */
467 bool FTPConn::GoPasv()
468 {
469    /* The PASV command only works on IPv4 sockets, even though it could
470       in theory suppory IPv6 via an all zeros reply */
471    if (((struct sockaddr *)&PeerAddr)->sa_family != AF_INET || 
472        ForceExtended == true)
473       return ExtGoPasv();
474    
475    if (PasvAddr != 0)
476       freeaddrinfo(PasvAddr);
477    PasvAddr = 0;
478    
479    // Try to enable pasv mode
480    unsigned int Tag;
481    string Msg;
482    if (WriteMsg(Tag,Msg,"PASV") == false)
483       return false;
484    
485    // Unsupported function
486    string::size_type Pos = Msg.find('(');
487    if (Tag >= 400 || Pos == string::npos)
488       return true;
489
490    // Scan it
491    unsigned a0,a1,a2,a3,p0,p1;
492    if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
493       return true;
494    
495    /* Some evil servers return 0 to mean their addr. We can actually speak
496       to these servers natively using IPv6 */
497    if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
498    {
499       // Get the IP in text form
500       char Name[NI_MAXHOST];
501       char Service[NI_MAXSERV];
502       getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
503                   Name,sizeof(Name),Service,sizeof(Service),
504                   NI_NUMERICHOST|NI_NUMERICSERV);
505       
506       struct addrinfo Hints;
507       memset(&Hints,0,sizeof(Hints));
508       Hints.ai_socktype = SOCK_STREAM;
509       Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
510       Hints.ai_flags |= AI_NUMERICHOST;
511       
512       // Get a new passive address.
513       char Port[100];
514       snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
515       if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
516          return true;
517       return true;
518    }
519    
520    struct addrinfo Hints;
521    memset(&Hints,0,sizeof(Hints));
522    Hints.ai_socktype = SOCK_STREAM;
523    Hints.ai_family = AF_INET;
524    Hints.ai_flags |= AI_NUMERICHOST;
525    
526    // Get a new passive address.
527    char Port[100];
528    snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
529    char Name[100];
530    snprintf(Name,sizeof(Name),"%u.%u.%u.%u",a0,a1,a2,a3);
531    if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
532       return true;
533    return true;
534 }
535                                                                         /*}}}*/
536 // FTPConn::ExtGoPasv - Enter Extended Passive mode                     /*{{{*/
537 // ---------------------------------------------------------------------
538 /* Try to enter extended passive mode. See GoPasv above and RFC 2428 */
539 bool FTPConn::ExtGoPasv()
540 {
541    if (PasvAddr != 0)
542       freeaddrinfo(PasvAddr);
543    PasvAddr = 0;
544    
545    // Try to enable pasv mode
546    unsigned int Tag;
547    string Msg;
548    if (WriteMsg(Tag,Msg,"EPSV") == false)
549       return false;
550    
551    // Unsupported function
552    string::size_type Pos = Msg.find('(');
553    if (Tag >= 400 || Pos == string::npos)
554       return true;
555
556    // Scan it   
557    string::const_iterator List[4];
558    unsigned Count = 0;
559    Pos++;
560    for (string::const_iterator I = Msg.begin() + Pos; I < Msg.end(); I++)
561    {
562       if (*I != Msg[Pos])
563          continue;
564       if (Count >= 4)
565          return true;
566       List[Count++] = I;
567    }
568    if (Count != 4)
569       return true;
570    
571    // Break it up ..
572    unsigned long Proto = 0;
573    unsigned long Port = 0;
574    string IP;
575    IP = string(List[1]+1,List[2]);
576    Port = atoi(string(List[2]+1,List[3]).c_str());
577    if (IP.empty() == false)
578       Proto = atoi(string(List[0]+1,List[1]).c_str());
579    
580    if (Port == 0)
581       return false;
582
583    // String version of the port
584    char PStr[100];
585    snprintf(PStr,sizeof(PStr),"%lu",Port);
586
587    // Get the IP in text form
588    struct addrinfo Hints;
589    memset(&Hints,0,sizeof(Hints));
590    Hints.ai_socktype = SOCK_STREAM;
591    Hints.ai_flags |= AI_NUMERICHOST;
592    
593    /* The RFC defined case, connect to the old IP/protocol using the
594       new port. */
595    if (IP.empty() == true)
596    {
597       // Get the IP in text form
598       char Name[NI_MAXHOST];
599       char Service[NI_MAXSERV];
600       getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
601                   Name,sizeof(Name),Service,sizeof(Service),
602                   NI_NUMERICHOST|NI_NUMERICSERV);
603       IP = Name;
604       Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
605    }
606    else
607    {
608       // Get the family..
609       Hints.ai_family = 0;
610       for (unsigned J = 0; AFMap[J].Family != 0; J++)
611          if (AFMap[J].IETFFamily == Proto)
612             Hints.ai_family = AFMap[J].Family;
613       if (Hints.ai_family == 0)
614          return true;
615    }
616    
617    // Get a new passive address.
618    int Res;
619    if ((Res = getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr)) != 0)
620       return true;
621    
622    return true;
623 }
624                                                                         /*}}}*/
625 // FTPConn::Size - Return the size of a file                            /*{{{*/
626 // ---------------------------------------------------------------------
627 /* Grab the file size from the server, 0 means no size or empty file */
628 bool FTPConn::Size(const char *Path,unsigned long &Size)
629 {
630    // Query the size
631    unsigned int Tag;
632    string Msg;
633    Size = 0;
634    if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
635       return false;
636    
637    char *End;
638    Size = strtol(Msg.c_str(),&End,10);
639    if (Tag >= 400 || End == Msg.c_str())
640       Size = 0;
641    return true;
642 }
643                                                                         /*}}}*/
644 // FTPConn::ModTime - Return the modification time of the file          /*{{{*/
645 // ---------------------------------------------------------------------
646 /* Like Size no error is returned if the command is not supported. If the
647    command fails then time is set to the current time of day to fool 
648    date checks. */
649 bool FTPConn::ModTime(const char *Path, time_t &Time)
650 {
651    Time = time(&Time);
652    
653    // Query the mod time
654    unsigned int Tag;
655    string Msg;
656    if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
657       return false;
658    if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
659       return true;
660    
661    // Parse it
662    StrToTime(Msg,Time);
663    return true;
664 }
665                                                                         /*}}}*/
666 // FTPConn::CreateDataFd - Get a data connection                        /*{{{*/
667 // ---------------------------------------------------------------------
668 /* Create the data connection. Call FinalizeDataFd after this though.. */
669 bool FTPConn::CreateDataFd()
670 {
671    close(DataFd);
672    DataFd = -1;
673    
674    // Attempt to enter passive mode.
675    if (TryPassive == true)
676    {
677       if (GoPasv() == false)
678          return false;
679       
680       // Oops, didn't work out, don't bother trying again.
681       if (PasvAddr == 0)
682          TryPassive = false;
683    }
684    
685    // Passive mode?
686    if (PasvAddr != 0)
687    {
688       // Get a socket
689       if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
690                            PasvAddr->ai_protocol)) < 0)
691          return _error->Errno("socket",_("Could not create a socket"));
692       
693       // Connect to the server
694       SetNonBlock(DataFd,true);
695       if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
696           errno != EINPROGRESS)
697          return _error->Errno("socket",_("Could not create a socket"));
698    
699       /* This implements a timeout for connect by opening the connection
700          nonblocking */
701       if (WaitFd(DataFd,true,TimeOut) == false)
702          return _error->Error(_("Could not connect data socket, connection timed out"));
703       unsigned int Err;
704       unsigned int Len = sizeof(Err);
705       if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
706          return _error->Errno("getsockopt",_("Failed"));
707       if (Err != 0)
708          return _error->Error(_("Could not connect passive socket."));
709
710       return true;
711    }
712    
713    // Port mode :<
714    close(DataListenFd);
715    DataListenFd = -1;
716
717    // Get the information for a listening socket.
718    struct addrinfo *BindAddr = 0;
719    struct addrinfo Hints;
720    memset(&Hints,0,sizeof(Hints));
721    Hints.ai_socktype = SOCK_STREAM;
722    Hints.ai_flags |= AI_PASSIVE;
723    Hints.ai_family = ((struct sockaddr *)&ServerAddr)->sa_family;
724    int Res;
725    if ((Res = getaddrinfo(0,"0",&Hints,&BindAddr)) != 0)
726       return _error->Error(_("getaddrinfo was unable to get a listening socket"));
727    
728    // Construct the socket
729    if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
730                               BindAddr->ai_protocol)) < 0)
731    {
732       freeaddrinfo(BindAddr);
733       return _error->Errno("socket",_("Could not create a socket"));
734    }
735    
736    // Bind and listen
737    if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
738    {
739       freeaddrinfo(BindAddr);
740       return _error->Errno("bind",_("Could not bind a socket"));
741    }
742    freeaddrinfo(BindAddr);   
743    if (listen(DataListenFd,1) < 0)
744       return _error->Errno("listen",_("Could not listen on the socket"));
745    SetNonBlock(DataListenFd,true);
746    
747    // Determine the name to send to the remote
748    struct sockaddr_storage Addr;
749    socklen_t AddrLen = sizeof(Addr);
750    if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
751       return _error->Errno("getsockname",_("Could not determine the socket's name"));
752
753
754    // Reverse the address. We need the server address and the data port.
755    char Name[NI_MAXHOST];
756    char Service[NI_MAXSERV];
757    char Service2[NI_MAXSERV];
758    getnameinfo((struct sockaddr *)&Addr,AddrLen,
759                Name,sizeof(Name),Service,sizeof(Service),
760                NI_NUMERICHOST|NI_NUMERICSERV);
761    getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
762                Name,sizeof(Name),Service2,sizeof(Service2),
763                NI_NUMERICHOST|NI_NUMERICSERV);
764
765    // Send off an IPv4 address in the old port format
766    if (((struct sockaddr *)&Addr)->sa_family == AF_INET && 
767        ForceExtended == false)
768    {
769       // Convert the dots in the quad into commas
770       for (char *I = Name; *I != 0; I++)
771          if (*I == '.')
772             *I = ',';
773       unsigned long Port = atoi(Service);
774       
775       // Send the port command
776       unsigned int Tag;
777       string Msg;
778       if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
779                    Name,
780                    (int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
781          return false;
782       if (Tag >= 400)
783          return _error->Error(_("Unable to send PORT command"));
784       return true;
785    }
786
787    // Construct an EPRT command
788    unsigned Proto = 0;
789    for (unsigned J = 0; AFMap[J].Family != 0; J++)
790       if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
791          Proto = AFMap[J].IETFFamily;
792    if (Proto == 0)
793       return _error->Error(_("Unknown address family %u (AF_*)"),
794                            ((struct sockaddr *)&Addr)->sa_family);
795    
796    // Send the EPRT command
797    unsigned int Tag;
798    string Msg;
799    if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
800       return false;
801    if (Tag >= 400)
802       return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
803    return true;
804 }
805                                                                         /*}}}*/
806 // FTPConn::Finalize - Complete the Data connection                     /*{{{*/
807 // ---------------------------------------------------------------------
808 /* If the connection is in port mode this waits for the other end to hook
809    up to us. */
810 bool FTPConn::Finalize()
811 {
812    // Passive mode? Do nothing
813    if (PasvAddr != 0)
814       return true;
815    
816    // Close any old socket..
817    close(DataFd);
818    DataFd = -1;
819    
820    // Wait for someone to connect..
821    if (WaitFd(DataListenFd,false,TimeOut) == false)
822       return _error->Error(_("Data socket connect timed out"));
823       
824    // Accept the connection
825    struct sockaddr_in Addr;
826    socklen_t Len = sizeof(Addr);
827    DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
828    if (DataFd < 0)
829       return _error->Errno("accept",_("Unable to accept connection"));
830
831    close(DataListenFd);
832    DataListenFd = -1;
833    
834    return true;
835 }
836                                                                         /*}}}*/
837 // FTPConn::Get - Get a file                                            /*{{{*/
838 // ---------------------------------------------------------------------
839 /* This opens a data connection, sends REST and RETR and then
840    transfers the file over. */
841 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
842                   Hashes &Hash,bool &Missing)
843 {
844    Missing = false;
845    if (CreateDataFd() == false)
846       return false;
847
848    unsigned int Tag;
849    string Msg;   
850    if (Resume != 0)
851    {      
852       if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
853          return false;
854       if (Tag >= 400)
855          Resume = 0;
856    }
857    
858    if (To.Truncate(Resume) == false)
859       return false;
860
861    if (To.Seek(0) == false)
862       return false;
863    
864    if (Resume != 0)
865    {
866       if (Hash.AddFD(To.Fd(),Resume) == false)
867       {
868          _error->Errno("read",_("Problem hashing file"));
869          return false;
870       }
871    }
872    
873    // Send the get command
874    if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
875       return false;
876    
877    if (Tag >= 400)
878    {
879       if (Tag == 550)
880          Missing = true;
881       return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
882    }
883    
884    // Finish off the data connection
885    if (Finalize() == false)
886       return false;
887    
888    // Copy loop
889    unsigned char Buffer[4096];
890    while (1)
891    {
892       // Wait for some data..
893       if (WaitFd(DataFd,false,TimeOut) == false)
894       {
895          Close();
896          return _error->Error(_("Data socket timed out"));
897       }
898       
899       // Read the data..
900       int Res = read(DataFd,Buffer,sizeof(Buffer));
901       if (Res == 0)
902          break;
903       if (Res < 0)
904       {
905          if (errno == EAGAIN)
906             continue;
907          break;
908       }
909    
910       Hash.Add(Buffer,Res);
911       if (To.Write(Buffer,Res) == false)
912       {
913          Close();
914          return false;
915       }      
916    }
917
918    // All done
919    close(DataFd);
920    DataFd = -1;
921    
922    // Read the closing message from the server
923    if (ReadResp(Tag,Msg) == false)
924       return false;
925    if (Tag >= 400)
926       return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
927    return true;
928 }
929                                                                         /*}}}*/
930
931 // FtpMethod::FtpMethod - Constructor                                   /*{{{*/
932 // ---------------------------------------------------------------------
933 /* */
934 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
935 {
936    signal(SIGTERM,SigTerm);
937    signal(SIGINT,SigTerm);
938    
939    Server = 0;
940    FailFd = -1;
941 }
942                                                                         /*}}}*/
943 // FtpMethod::SigTerm - Handle a fatal signal                           /*{{{*/
944 // ---------------------------------------------------------------------
945 /* This closes and timestamps the open file. This is neccessary to get 
946    resume behavoir on user abort */
947 void FtpMethod::SigTerm(int)
948 {
949    if (FailFd == -1)
950       _exit(100);
951    close(FailFd);
952    
953    // Timestamp
954    struct utimbuf UBuf;
955    UBuf.actime = FailTime;
956    UBuf.modtime = FailTime;
957    utime(FailFile.c_str(),&UBuf);
958    
959    _exit(100);
960 }
961                                                                         /*}}}*/
962 // FtpMethod::Configuration - Handle a configuration message            /*{{{*/
963 // ---------------------------------------------------------------------
964 /* We stash the desired pipeline depth */
965 bool FtpMethod::Configuration(string Message)
966 {
967    if (pkgAcqMethod::Configuration(Message) == false)
968       return false;
969    
970    TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
971    return true;
972 }
973                                                                         /*}}}*/
974 // FtpMethod::Fetch - Fetch a file                                      /*{{{*/
975 // ---------------------------------------------------------------------
976 /* Fetch a single file, called by the base class..  */
977 bool FtpMethod::Fetch(FetchItem *Itm)
978 {
979    URI Get = Itm->Uri;
980    const char *File = Get.Path.c_str();
981    FetchResult Res;
982    Res.Filename = Itm->DestFile;
983    Res.IMSHit = false;
984    
985    // Connect to the server
986    if (Server == 0 || Server->Comp(Get) == false)
987    {
988       delete Server;
989       Server = new FTPConn(Get);
990    }
991   
992    // Could not connect is a transient error..
993    if (Server->Open(this) == false)
994    {
995       Server->Close();
996       Fail(true);
997       return true;
998    }
999    
1000    // Get the files information
1001    Status(_("Query"));
1002    unsigned long Size;
1003    if (Server->Size(File,Size) == false ||
1004        Server->ModTime(File,FailTime) == false)
1005    {
1006       Fail(true);
1007       return true;
1008    }
1009    Res.Size = Size;
1010
1011    // See if it is an IMS hit
1012    if (Itm->LastModified == FailTime)
1013    {
1014       Res.Size = 0;
1015       Res.IMSHit = true;
1016       URIDone(Res);
1017       return true;
1018    }
1019    
1020    // See if the file exists
1021    struct stat Buf;
1022    if (stat(Itm->DestFile.c_str(),&Buf) == 0)
1023    {
1024       if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
1025       {
1026          Res.Size = Buf.st_size;
1027          Res.LastModified = Buf.st_mtime;
1028          Res.ResumePoint = Buf.st_size;
1029          URIDone(Res);
1030          return true;
1031       }
1032       
1033       // Resume?
1034       if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
1035          Res.ResumePoint = Buf.st_size;
1036    }
1037    
1038    // Open the file
1039    Hashes Hash;
1040    {
1041       FileFd Fd(Itm->DestFile,FileFd::WriteAny);
1042       if (_error->PendingError() == true)
1043          return false;
1044       
1045       URIStart(Res);
1046       
1047       FailFile = Itm->DestFile;
1048       FailFile.c_str();   // Make sure we dont do a malloc in the signal handler
1049       FailFd = Fd.Fd();
1050       
1051       bool Missing;
1052       if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing) == false)
1053       {
1054          Fd.Close();
1055          
1056          // Timestamp
1057          struct utimbuf UBuf;
1058          UBuf.actime = FailTime;
1059          UBuf.modtime = FailTime;
1060          utime(FailFile.c_str(),&UBuf);
1061          
1062          // If the file is missing we hard fail otherwise transient fail
1063          if (Missing == true)
1064             return false;
1065          Fail(true);
1066          return true;
1067       }
1068
1069       Res.Size = Fd.Size();
1070    }
1071    
1072    Res.LastModified = FailTime;
1073    Res.TakeHashes(Hash);
1074    
1075    // Timestamp
1076    struct utimbuf UBuf;
1077    UBuf.actime = FailTime;
1078    UBuf.modtime = FailTime;
1079    utime(Queue->DestFile.c_str(),&UBuf);
1080    FailFd = -1;
1081
1082    URIDone(Res);
1083    
1084    return true;
1085 }
1086                                                                         /*}}}*/
1087
1088 int main(int argc,const char *argv[])
1089
1090    /* See if we should be come the http client - we do this for http
1091       proxy urls */
1092    if (getenv("ftp_proxy") != 0)
1093    {
1094       URI Proxy = string(getenv("ftp_proxy"));
1095       
1096       // Run the HTTP method
1097       if (Proxy.Access == "http")
1098       {
1099          // Copy over the environment setting
1100          char S[300];
1101          snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
1102          putenv(S);
1103          putenv("no_proxy=");
1104          
1105          // Run the http method
1106          string Path = flNotFile(argv[0]) + "http";
1107          execl(Path.c_str(),Path.c_str(),NULL);
1108          cerr << _("Unable to invoke ") << Path << endl;
1109          exit(100);
1110       }      
1111    }
1112    
1113    FtpMethod Mth;
1114    
1115    return Mth.Run();
1116 }