]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
ff9dac2212d9d3a5f46c9c20c8827e09706c388a
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.11 1999/05/27 05:51:18 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","Connect initiate the connection");
177 Peer
= *((struct sockaddr_in
*)LastHostAddr
->ai_addr
);
179 /* This implements a timeout for connect by opening the connection
181 if (WaitFd(ServerFd
,true,TimeOut
) == false)
182 return _error
->Error("Could not connect, connection timed out");
184 unsigned int Len
= sizeof(Err
);
185 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
186 return _error
->Errno("getsockopt","Failed");
188 return _error
->Error("Could not connect.");
190 Owner
->Status("Logging in");
194 // FTPConn::Login - Login to the remote server /*{{{*/
195 // ---------------------------------------------------------------------
196 /* This performs both normal login and proxy login using a simples script
197 stored in the config file. */
198 bool FTPConn::Login()
203 // Setup the variables needed for authentication
204 string User
= "anonymous";
205 string Pass
= "apt_get_ftp_2.0@debian.linux.user";
207 // Fill in the user/pass
208 if (ServerName
.User
.empty() == false)
209 User
= ServerName
.User
;
210 if (ServerName
.Password
.empty() == false)
211 Pass
= ServerName
.Password
;
213 // Perform simple login
214 if (Proxy
.empty() == true)
216 // Read the initial response
217 if (ReadResp(Tag
,Msg
) == false)
220 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
223 if (WriteMsg(Tag
,Msg
,"USER %s",User
.c_str()) == false)
226 return _error
->Error("USER failed, server said: %s",Msg
.c_str());
229 if (WriteMsg(Tag
,Msg
,"PASS %s",Pass
.c_str()) == false)
232 return _error
->Error("PASS failed, server said: %s",Msg
.c_str());
234 // Enter passive mode
235 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
236 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
238 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
242 // Read the initial response
243 if (ReadResp(Tag
,Msg
) == false)
246 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
248 // Perform proxy script execution
249 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
250 if (Opts
== 0 || Opts
->Child
== 0)
251 return _error
->Error("A proxy server was specified but no login "
252 "script, Acquire::ftp::ProxyLogin is empty.");
255 // Iterate over the entire login script
256 for (; Opts
!= 0; Opts
= Opts
->Next
)
258 if (Opts
->Value
.empty() == true)
261 // Substitute the variables into the command
263 if (ServerName
.Port
!= 0)
264 sprintf(SitePort
,"%u",ServerName
.Port
);
266 strcpy(SitePort
,"21");
267 string Tmp
= Opts
->Value
;
268 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
269 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
270 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
271 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
272 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
273 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
276 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
279 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
282 // Enter passive mode
284 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
285 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
288 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true)
289 TryPassive
= _config
->FindB("Acquire::FTP::Proxy::Passive",true);
291 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
296 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
299 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
304 // FTPConn::ReadLine - Read a line from the server /*{{{*/
305 // ---------------------------------------------------------------------
306 /* This performs a very simple buffered read. */
307 bool FTPConn::ReadLine(string
&Text
)
313 while (Len
< sizeof(Buffer
))
315 // Scan the buffer for a new line
316 for (unsigned int I
= 0; I
!= Len
; I
++)
318 // Escape some special chars
323 if (Buffer
[I
] != '\n')
327 Text
= string(Buffer
,I
);
328 memmove(Buffer
,Buffer
+I
,Len
- I
);
333 // Wait for some data..
334 if (WaitFd(ServerFd
,false,TimeOut
) == false)
337 return _error
->Error("Connection timeout");
341 int Res
= read(ServerFd
,Buffer
+ Len
,sizeof(Buffer
) - Len
);
345 return _error
->Errno("read","Read error");
350 return _error
->Error("A response overflowed the buffer.");
353 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
354 // ---------------------------------------------------------------------
355 /* This reads a reply code from the server, it handles both p */
356 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
358 // Grab the first line of the response
360 if (ReadLine(Msg
) == false)
365 Ret
= strtol(Msg
.c_str(),&End
,10);
366 if (End
- Msg
.c_str() != 3)
367 return _error
->Error("Protocol corruption");
370 Text
= Msg
.c_str()+4;
374 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
379 return _error
->Error("Protocol corruption");
381 /* Okay, here we do the continued message trick. This is foolish, but
382 proftpd follows the protocol as specified and wu-ftpd doesn't, so
383 we filter. I wonder how many clients break if you use proftpd and
384 put a '- in the 3rd spot in the message? */
386 strncpy(Leader
,Msg
.c_str(),3);
388 while (ReadLine(Msg
) == true)
390 // Short, it must be using RFC continuation..
391 if (Msg
.length() < 4)
398 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
400 Text
+= Msg
.c_str()+4;
404 // This message has the wu-ftpd style reply code prefixed
405 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
407 Text
+= Msg
.c_str()+4;
411 // Must be RFC style prefixing
415 if (Debug
== true && _error
->PendingError() == false)
416 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
418 return !_error
->PendingError();
421 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
422 // ---------------------------------------------------------------------
423 /* Simple printf like function.. */
424 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
429 // sprintf the description
431 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
435 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
438 unsigned long Len
= strlen(S
);
439 unsigned long Start
= 0;
442 if (WaitFd(ServerFd
,true,TimeOut
) == false)
445 return _error
->Error("Connection timeout");
448 int Res
= write(ServerFd
,S
+ Start
,Len
);
452 return _error
->Errno("write","Write Error");
459 return ReadResp(Ret
,Text
);
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 Borrowed mostly from lftp. We have to enter passive mode every time
467 we make a data connection :| */
468 bool FTPConn::GoPasv()
470 // Try to enable pasv mode
473 if (WriteMsg(Tag
,Msg
,"PASV") == false)
476 // Unsupported function
477 string::size_type Pos
= Msg
.find('(');
478 if (Tag
>= 400 || Pos
== string::npos
)
480 memset(&PasvAddr
,0,sizeof(PasvAddr
));
485 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
486 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
488 memset(&PasvAddr
,0,sizeof(PasvAddr
));
492 // lftp used this horrid byte order manipulation.. Ik.
493 PasvAddr
.sin_family
= AF_INET
;
496 a
= (unsigned char *)&PasvAddr
.sin_addr
;
497 p
= (unsigned char *)&PasvAddr
.sin_port
;
499 // Some evil servers return 0 to mean their addr
500 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
502 PasvAddr
.sin_addr
= Peer
.sin_addr
;
518 // FTPConn::Size - Return the size of a file /*{{{*/
519 // ---------------------------------------------------------------------
520 /* Grab the file size from the server, 0 means no size or empty file */
521 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
527 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
531 Size
= strtol(Msg
.c_str(),&End
,10);
532 if (Tag
>= 400 || End
== Msg
.c_str())
537 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
538 // ---------------------------------------------------------------------
539 /* Like Size no error is returned if the command is not supported. If the
540 command fails then time is set to the current time of day to fool
542 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
546 // Query the mod time
549 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
551 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
556 memset(&tm
,0,sizeof(tm
));
557 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
558 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
564 /* We use timegm from the GNU C library, libapt-pkg will provide this
565 symbol if it does not exist */
570 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
571 // ---------------------------------------------------------------------
572 /* Create the data connection. Call FinalizeDataFd after this though.. */
573 bool FTPConn::CreateDataFd()
578 // Attempt to enter passive mode.
579 if (TryPassive
== true)
581 if (GoPasv() == false)
584 // Oops, didn't work out, don't bother trying again.
585 if (PasvAddr
.sin_port
== 0)
590 if (PasvAddr
.sin_port
!= 0)
593 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
594 return _error
->Errno("socket","Could not create a socket");
596 // Connect to the server
597 SetNonBlock(DataFd
,true);
598 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
599 errno
!= EINPROGRESS
)
600 return _error
->Errno("socket","Could not create a socket");
602 /* This implements a timeout for connect by opening the connection
604 if (WaitFd(ServerFd
,true,TimeOut
) == false)
605 return _error
->Error("Could not connect data socket, connection timed out");
607 unsigned int Len
= sizeof(Err
);
608 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
609 return _error
->Errno("getsockopt","Failed");
611 return _error
->Error("Could not connect.");
621 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
622 return _error
->Errno("socket","Could not create a socket");
626 memset(&Addr
,0,sizeof(Addr
));
627 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
628 return _error
->Errno("bind","Could not bind a socket");
629 if (listen(DataListenFd
,1) < 0)
630 return _error
->Errno("listen","Could not listen on the socket");
631 SetNonBlock(DataListenFd
,true);
633 // Determine the name to send to the remote
635 socklen_t Jnk
= sizeof(Addr
);
636 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
637 return _error
->Errno("getsockname","Could not determine the socket's name");
639 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
640 return _error
->Errno("getsockname","Could not determine the socket's name");
642 // This bit ripped from qftp
643 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
644 unsigned long bp
= ntohs(Addr
.sin_port
);
646 // Send the port command
649 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
650 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
651 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
652 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
655 return _error
->Error("Unable to send port command");
660 // FTPConn::Finalize - Complete the Data connection /*{{{*/
661 // ---------------------------------------------------------------------
662 /* If the connection is in port mode this waits for the other end to hook
664 bool FTPConn::Finalize()
666 // Passive mode? Do nothing
667 if (PasvAddr
.sin_port
!= 0)
670 // Close any old socket..
674 // Wait for someone to connect..
675 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
676 return _error
->Error("Data socket connect timed out");
678 // Accept the connection
679 struct sockaddr_in Addr
;
680 socklen_t Len
= sizeof(Addr
);
681 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
683 return _error
->Errno("accept","Unable to accept connection");
691 // FTPConn::Get - Get a file /*{{{*/
692 // ---------------------------------------------------------------------
693 /* This opens a data connection, sends REST and RETR and then
694 transfers the file over. */
695 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
696 MD5Summation
&MD5
,bool &Missing
)
699 if (CreateDataFd() == false)
706 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
712 if (To
.Truncate(Resume
) == false)
715 if (To
.Seek(0) == false)
720 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
722 _error
->Errno("read","Problem hashing file");
727 // Send the get command
728 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
735 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
738 // Finish off the data connection
739 if (Finalize() == false)
743 unsigned char Buffer
[4096];
746 // Wait for some data..
747 if (WaitFd(DataFd
,false,TimeOut
) == false)
750 return _error
->Error("Data socket timed out");
754 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
765 if (To
.Write(Buffer
,Res
) == false)
776 // Read the closing message from the server
777 if (ReadResp(Tag
,Msg
) == false)
780 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
785 // FtpMethod::FtpMethod - Constructor /*{{{*/
786 // ---------------------------------------------------------------------
788 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
790 signal(SIGTERM
,SigTerm
);
791 signal(SIGINT
,SigTerm
);
797 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
798 // ---------------------------------------------------------------------
799 /* This closes and timestamps the open file. This is neccessary to get
800 resume behavoir on user abort */
801 void FtpMethod::SigTerm(int)
809 UBuf
.actime
= FailTime
;
810 UBuf
.modtime
= FailTime
;
811 utime(FailFile
.c_str(),&UBuf
);
816 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
817 // ---------------------------------------------------------------------
818 /* We stash the desired pipeline depth */
819 bool FtpMethod::Configuration(string Message
)
821 if (pkgAcqMethod::Configuration(Message
) == false)
824 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
828 // FtpMethod::Fetch - Fetch a file /*{{{*/
829 // ---------------------------------------------------------------------
830 /* Fetch a single file, called by the base class.. */
831 bool FtpMethod::Fetch(FetchItem
*Itm
)
834 const char *File
= Get
.Path
.c_str();
836 Res
.Filename
= Itm
->DestFile
;
839 // Connect to the server
840 if (Server
== 0 || Server
->Comp(Get
) == false)
843 Server
= new FTPConn(Get
);
846 // Could not connect is a transient error..
847 if (Server
->Open(this) == false)
853 // Get the files information
856 if (Server
->Size(File
,Size
) == false ||
857 Server
->ModTime(File
,FailTime
) == false)
864 // See if it is an IMS hit
865 if (Itm
->LastModified
== FailTime
)
873 // See if the file exists
875 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
877 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
879 Res
.Size
= Buf
.st_size
;
880 Res
.LastModified
= Buf
.st_mtime
;
886 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
887 Res
.ResumePoint
= Buf
.st_size
;
893 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
894 if (_error
->PendingError() == true)
899 FailFile
= Itm
->DestFile
;
900 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
904 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
906 // If the file is missing we hard fail otherwise transient fail
913 Res
.Size
= Fd
.Size();
916 Res
.LastModified
= FailTime
;
917 Res
.MD5Sum
= MD5
.Result();
922 UBuf
.actime
= FailTime
;
923 UBuf
.modtime
= FailTime
;
924 utime(Queue
->DestFile
.c_str(),&UBuf
);
933 int main(int argc
,const char *argv
[])
935 /* See if we should be come the http client - we do this for http
937 if (getenv("ftp_proxy") != 0)
939 URI Proxy
= string(getenv("ftp_proxy"));
940 if (Proxy
.Access
== "http")
942 // Copy over the environment setting
944 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
947 // Run the http method
948 string Path
= flNotFile(argv
[0]) + "/http";
949 execl(Path
.c_str(),Path
.c_str(),0);
950 cerr
<< "Unable to invoke " << Path
<< endl
;