]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.12 1999/05/28 07:04:45 jgg Exp $
4 /* ######################################################################
6 HTTP Aquire Method - This is the FTP aquire method for APT.
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.
13 RFC 2428 describes the IPv6 FTP behavior
15 ##################################################################### */
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/md5.h>
33 #include <netinet/in.h>
34 #include <sys/socket.h>
35 #include <arpa/inet.h>
38 #include "rfc2553emu.h"
42 unsigned long TimeOut
= 120;
44 string
FtpMethod::FailFile
;
45 int FtpMethod::FailFd
= -1;
46 time_t FtpMethod::FailTime
= 0;
48 // FTPConn::FTPConn - Constructor /*{{{*/
49 // ---------------------------------------------------------------------
51 FTPConn::FTPConn(URI Srv
) : Len(0), ServerFd(-1), DataFd(-1),
52 DataListenFd(-1), ServerName(Srv
)
54 Debug
= _config
->FindB("Debug::Acquire::Ftp",false);
55 memset(&PasvAddr
,0,sizeof(PasvAddr
));
58 // FTPConn::~FTPConn - Destructor /*{{{*/
59 // ---------------------------------------------------------------------
66 // FTPConn::Close - Close down the connection /*{{{*/
67 // ---------------------------------------------------------------------
68 /* Just tear down the socket and data socket */
77 memset(&PasvAddr
,0,sizeof(PasvAddr
));
80 // FTPConn::Open - Open a new connection /*{{{*/
81 // ---------------------------------------------------------------------
82 /* Connect to the server using a non-blocking connection and perform a
86 struct addrinfo
*LastHostAddr
= 0;
87 bool FTPConn::Open(pkgAcqMethod
*Owner
)
89 // Use the already open connection if possible.
95 // Determine the proxy setting
96 if (getenv("ftp_proxy") == 0)
98 string DefProxy
= _config
->Find("Acquire::ftp::Proxy");
99 string SpecificProxy
= _config
->Find("Acquire::ftp::Proxy::" + ServerName
.Host
);
100 if (SpecificProxy
.empty() == false)
102 if (SpecificProxy
== "DIRECT")
105 Proxy
= SpecificProxy
;
111 Proxy
= getenv("ftp_proxy");
113 // Determine what host and port to use based on the proxy settings
116 if (Proxy
.empty() == true)
118 if (ServerName
.Port
!= 0)
119 Port
= ServerName
.Port
;
120 Host
= ServerName
.Host
;
129 /* We used a cached address record.. Yes this is against the spec but
130 the way we have setup our rotating dns suggests that this is more
132 if (LastHost
!= Host
|| LastPort
!= Port
)
134 Owner
->Status("Connecting to %s",Host
.c_str());
139 snprintf(S
,sizeof(S
),"%u",Port
);
141 // Free the old address structure
142 if (LastHostAddr
!= 0)
144 freeaddrinfo(LastHostAddr
);
148 // We only understand SOCK_STREAM sockets.
149 struct addrinfo Hints
;
150 memset(&Hints
,0,sizeof(Hints
));
151 Hints
.ai_socktype
= SOCK_STREAM
;
153 // Resolve both the host and service simultaneously
154 if (getaddrinfo(Host
.c_str(),S
,&Hints
,&LastHostAddr
) != 0 ||
156 return _error
->Error("Could not resolve '%s'",Host
.c_str());
162 // Get the printable IP address
163 char Name
[NI_MAXHOST
];
165 getnameinfo(LastHostAddr
->ai_addr
,LastHostAddr
->ai_addrlen
,
166 Name
,sizeof(Name
),0,0,NI_NUMERICHOST
);
167 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),Name
);
170 if ((ServerFd
= socket(LastHostAddr
->ai_family
,LastHostAddr
->ai_socktype
,
171 LastHostAddr
->ai_protocol
)) < 0)
172 return _error
->Errno("socket","Could not create a socket");
173 SetNonBlock(ServerFd
,true);
174 if (connect(ServerFd
,LastHostAddr
->ai_addr
,LastHostAddr
->ai_addrlen
) < 0 &&
175 errno
!= EINPROGRESS
)
176 return _error
->Errno("connect","Cannot initiate the connection "
177 "to %s (%s).",Host
.c_str(),Name
);
178 Peer
= *((struct sockaddr_in
*)LastHostAddr
->ai_addr
);
180 /* This implements a timeout for connect by opening the connection
182 if (WaitFd(ServerFd
,true,TimeOut
) == false)
183 return _error
->Error("Could not connect to %s (%s), "
184 "connection timed out",Host
.c_str(),Name
);
186 unsigned int Len
= sizeof(Err
);
187 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
188 return _error
->Errno("getsockopt","Failed");
190 return _error
->Error("Could not connect to %s (%s).",Host
.c_str(),Name
);
192 Owner
->Status("Logging in");
196 // FTPConn::Login - Login to the remote server /*{{{*/
197 // ---------------------------------------------------------------------
198 /* This performs both normal login and proxy login using a simples script
199 stored in the config file. */
200 bool FTPConn::Login()
205 // Setup the variables needed for authentication
206 string User
= "anonymous";
207 string Pass
= "apt_get_ftp_2.0@debian.linux.user";
209 // Fill in the user/pass
210 if (ServerName
.User
.empty() == false)
211 User
= ServerName
.User
;
212 if (ServerName
.Password
.empty() == false)
213 Pass
= ServerName
.Password
;
215 // Perform simple login
216 if (Proxy
.empty() == true)
218 // Read the initial response
219 if (ReadResp(Tag
,Msg
) == false)
222 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
225 if (WriteMsg(Tag
,Msg
,"USER %s",User
.c_str()) == false)
228 return _error
->Error("USER failed, server said: %s",Msg
.c_str());
231 if (WriteMsg(Tag
,Msg
,"PASS %s",Pass
.c_str()) == false)
234 return _error
->Error("PASS failed, server said: %s",Msg
.c_str());
236 // Enter passive mode
237 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
238 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
240 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
244 // Read the initial response
245 if (ReadResp(Tag
,Msg
) == false)
248 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
250 // Perform proxy script execution
251 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
252 if (Opts
== 0 || Opts
->Child
== 0)
253 return _error
->Error("A proxy server was specified but no login "
254 "script, Acquire::ftp::ProxyLogin is empty.");
257 // Iterate over the entire login script
258 for (; Opts
!= 0; Opts
= Opts
->Next
)
260 if (Opts
->Value
.empty() == true)
263 // Substitute the variables into the command
265 if (ServerName
.Port
!= 0)
266 sprintf(SitePort
,"%u",ServerName
.Port
);
268 strcpy(SitePort
,"21");
269 string Tmp
= Opts
->Value
;
270 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
271 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
272 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
273 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
274 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
275 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
278 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
281 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
284 // Enter passive mode
286 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
287 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
290 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true)
291 TryPassive
= _config
->FindB("Acquire::FTP::Proxy::Passive",true);
293 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
298 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
301 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
306 // FTPConn::ReadLine - Read a line from the server /*{{{*/
307 // ---------------------------------------------------------------------
308 /* This performs a very simple buffered read. */
309 bool FTPConn::ReadLine(string
&Text
)
315 while (Len
< sizeof(Buffer
))
317 // Scan the buffer for a new line
318 for (unsigned int I
= 0; I
!= Len
; I
++)
320 // Escape some special chars
325 if (Buffer
[I
] != '\n')
329 Text
= string(Buffer
,I
);
330 memmove(Buffer
,Buffer
+I
,Len
- I
);
335 // Wait for some data..
336 if (WaitFd(ServerFd
,false,TimeOut
) == false)
339 return _error
->Error("Connection timeout");
343 int Res
= read(ServerFd
,Buffer
+ Len
,sizeof(Buffer
) - Len
);
347 return _error
->Errno("read","Read error");
352 return _error
->Error("A response overflowed the buffer.");
355 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
356 // ---------------------------------------------------------------------
357 /* This reads a reply code from the server, it handles both p */
358 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
360 // Grab the first line of the response
362 if (ReadLine(Msg
) == false)
367 Ret
= strtol(Msg
.c_str(),&End
,10);
368 if (End
- Msg
.c_str() != 3)
369 return _error
->Error("Protocol corruption");
372 Text
= Msg
.c_str()+4;
376 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
381 return _error
->Error("Protocol corruption");
383 /* Okay, here we do the continued message trick. This is foolish, but
384 proftpd follows the protocol as specified and wu-ftpd doesn't, so
385 we filter. I wonder how many clients break if you use proftpd and
386 put a '- in the 3rd spot in the message? */
388 strncpy(Leader
,Msg
.c_str(),3);
390 while (ReadLine(Msg
) == true)
392 // Short, it must be using RFC continuation..
393 if (Msg
.length() < 4)
400 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
402 Text
+= Msg
.c_str()+4;
406 // This message has the wu-ftpd style reply code prefixed
407 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
409 Text
+= Msg
.c_str()+4;
413 // Must be RFC style prefixing
417 if (Debug
== true && _error
->PendingError() == false)
418 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
420 return !_error
->PendingError();
423 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
424 // ---------------------------------------------------------------------
425 /* Simple printf like function.. */
426 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
431 // sprintf the description
433 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
437 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
440 unsigned long Len
= strlen(S
);
441 unsigned long Start
= 0;
444 if (WaitFd(ServerFd
,true,TimeOut
) == false)
447 return _error
->Error("Connection timeout");
450 int Res
= write(ServerFd
,S
+ Start
,Len
);
454 return _error
->Errno("write","Write Error");
461 return ReadResp(Ret
,Text
);
464 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
465 // ---------------------------------------------------------------------
466 /* Try to enter passive mode, the return code does not indicate if passive
467 mode could or could not be established, only if there was a fatal error.
468 Borrowed mostly from lftp. We have to enter passive mode every time
469 we make a data connection :| */
470 bool FTPConn::GoPasv()
472 // Try to enable pasv mode
475 if (WriteMsg(Tag
,Msg
,"PASV") == false)
478 // Unsupported function
479 string::size_type Pos
= Msg
.find('(');
480 if (Tag
>= 400 || Pos
== string::npos
)
482 memset(&PasvAddr
,0,sizeof(PasvAddr
));
487 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
488 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
490 memset(&PasvAddr
,0,sizeof(PasvAddr
));
494 // lftp used this horrid byte order manipulation.. Ik.
495 PasvAddr
.sin_family
= AF_INET
;
498 a
= (unsigned char *)&PasvAddr
.sin_addr
;
499 p
= (unsigned char *)&PasvAddr
.sin_port
;
501 // Some evil servers return 0 to mean their addr
502 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
504 PasvAddr
.sin_addr
= Peer
.sin_addr
;
520 // FTPConn::Size - Return the size of a file /*{{{*/
521 // ---------------------------------------------------------------------
522 /* Grab the file size from the server, 0 means no size or empty file */
523 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
529 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
533 Size
= strtol(Msg
.c_str(),&End
,10);
534 if (Tag
>= 400 || End
== Msg
.c_str())
539 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
540 // ---------------------------------------------------------------------
541 /* Like Size no error is returned if the command is not supported. If the
542 command fails then time is set to the current time of day to fool
544 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
548 // Query the mod time
551 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
553 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
558 memset(&tm
,0,sizeof(tm
));
559 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
560 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
566 /* We use timegm from the GNU C library, libapt-pkg will provide this
567 symbol if it does not exist */
572 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
573 // ---------------------------------------------------------------------
574 /* Create the data connection. Call FinalizeDataFd after this though.. */
575 bool FTPConn::CreateDataFd()
580 // Attempt to enter passive mode.
581 if (TryPassive
== true)
583 if (GoPasv() == false)
586 // Oops, didn't work out, don't bother trying again.
587 if (PasvAddr
.sin_port
== 0)
592 if (PasvAddr
.sin_port
!= 0)
595 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
596 return _error
->Errno("socket","Could not create a socket");
598 // Connect to the server
599 SetNonBlock(DataFd
,true);
600 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
601 errno
!= EINPROGRESS
)
602 return _error
->Errno("socket","Could not create a socket");
604 /* This implements a timeout for connect by opening the connection
606 if (WaitFd(ServerFd
,true,TimeOut
) == false)
607 return _error
->Error("Could not connect data socket, connection timed out");
609 unsigned int Len
= sizeof(Err
);
610 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
611 return _error
->Errno("getsockopt","Failed");
613 return _error
->Error("Could not connect.");
623 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
624 return _error
->Errno("socket","Could not create a socket");
628 memset(&Addr
,0,sizeof(Addr
));
629 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
630 return _error
->Errno("bind","Could not bind a socket");
631 if (listen(DataListenFd
,1) < 0)
632 return _error
->Errno("listen","Could not listen on the socket");
633 SetNonBlock(DataListenFd
,true);
635 // Determine the name to send to the remote
637 socklen_t Jnk
= sizeof(Addr
);
638 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
639 return _error
->Errno("getsockname","Could not determine the socket's name");
641 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
642 return _error
->Errno("getsockname","Could not determine the socket's name");
644 // This bit ripped from qftp
645 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
646 unsigned long bp
= ntohs(Addr
.sin_port
);
648 // Send the port command
651 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
652 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
653 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
654 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
657 return _error
->Error("Unable to send port command");
662 // FTPConn::Finalize - Complete the Data connection /*{{{*/
663 // ---------------------------------------------------------------------
664 /* If the connection is in port mode this waits for the other end to hook
666 bool FTPConn::Finalize()
668 // Passive mode? Do nothing
669 if (PasvAddr
.sin_port
!= 0)
672 // Close any old socket..
676 // Wait for someone to connect..
677 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
678 return _error
->Error("Data socket connect timed out");
680 // Accept the connection
681 struct sockaddr_in Addr
;
682 socklen_t Len
= sizeof(Addr
);
683 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
685 return _error
->Errno("accept","Unable to accept connection");
693 // FTPConn::Get - Get a file /*{{{*/
694 // ---------------------------------------------------------------------
695 /* This opens a data connection, sends REST and RETR and then
696 transfers the file over. */
697 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
698 MD5Summation
&MD5
,bool &Missing
)
701 if (CreateDataFd() == false)
708 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
714 if (To
.Truncate(Resume
) == false)
717 if (To
.Seek(0) == false)
722 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
724 _error
->Errno("read","Problem hashing file");
729 // Send the get command
730 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
737 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
740 // Finish off the data connection
741 if (Finalize() == false)
745 unsigned char Buffer
[4096];
748 // Wait for some data..
749 if (WaitFd(DataFd
,false,TimeOut
) == false)
752 return _error
->Error("Data socket timed out");
756 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
767 if (To
.Write(Buffer
,Res
) == false)
778 // Read the closing message from the server
779 if (ReadResp(Tag
,Msg
) == false)
782 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
787 // FtpMethod::FtpMethod - Constructor /*{{{*/
788 // ---------------------------------------------------------------------
790 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
792 signal(SIGTERM
,SigTerm
);
793 signal(SIGINT
,SigTerm
);
799 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
800 // ---------------------------------------------------------------------
801 /* This closes and timestamps the open file. This is neccessary to get
802 resume behavoir on user abort */
803 void FtpMethod::SigTerm(int)
811 UBuf
.actime
= FailTime
;
812 UBuf
.modtime
= FailTime
;
813 utime(FailFile
.c_str(),&UBuf
);
818 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
819 // ---------------------------------------------------------------------
820 /* We stash the desired pipeline depth */
821 bool FtpMethod::Configuration(string Message
)
823 if (pkgAcqMethod::Configuration(Message
) == false)
826 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
830 // FtpMethod::Fetch - Fetch a file /*{{{*/
831 // ---------------------------------------------------------------------
832 /* Fetch a single file, called by the base class.. */
833 bool FtpMethod::Fetch(FetchItem
*Itm
)
836 const char *File
= Get
.Path
.c_str();
838 Res
.Filename
= Itm
->DestFile
;
841 // Connect to the server
842 if (Server
== 0 || Server
->Comp(Get
) == false)
845 Server
= new FTPConn(Get
);
848 // Could not connect is a transient error..
849 if (Server
->Open(this) == false)
856 // Get the files information
859 if (Server
->Size(File
,Size
) == false ||
860 Server
->ModTime(File
,FailTime
) == false)
867 // See if it is an IMS hit
868 if (Itm
->LastModified
== FailTime
)
876 // See if the file exists
878 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
880 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
882 Res
.Size
= Buf
.st_size
;
883 Res
.LastModified
= Buf
.st_mtime
;
889 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
890 Res
.ResumePoint
= Buf
.st_size
;
896 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
897 if (_error
->PendingError() == true)
902 FailFile
= Itm
->DestFile
;
903 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
907 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
909 // If the file is missing we hard fail otherwise transient fail
916 Res
.Size
= Fd
.Size();
919 Res
.LastModified
= FailTime
;
920 Res
.MD5Sum
= MD5
.Result();
925 UBuf
.actime
= FailTime
;
926 UBuf
.modtime
= FailTime
;
927 utime(Queue
->DestFile
.c_str(),&UBuf
);
936 int main(int argc
,const char *argv
[])
938 /* See if we should be come the http client - we do this for http
940 if (getenv("ftp_proxy") != 0)
942 URI Proxy
= string(getenv("ftp_proxy"));
943 if (Proxy
.Access
== "http")
945 // Copy over the environment setting
947 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
950 // Run the http method
951 string Path
= flNotFile(argv
[0]) + "/http";
952 execl(Path
.c_str(),Path
.c_str(),0);
953 cerr
<< "Unable to invoke " << Path
<< endl
;