]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.20 2000/06/18 04:19:39 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"
43 unsigned long TimeOut
= 120;
45 string
FtpMethod::FailFile
;
46 int FtpMethod::FailFd
= -1;
47 time_t FtpMethod::FailTime
= 0;
49 // FTPConn::FTPConn - Constructor /*{{{*/
50 // ---------------------------------------------------------------------
52 FTPConn::FTPConn(URI Srv
) : Len(0), ServerFd(-1), DataFd(-1),
53 DataListenFd(-1), ServerName(Srv
)
55 Debug
= _config
->FindB("Debug::Acquire::Ftp",false);
56 memset(&PasvAddr
,0,sizeof(PasvAddr
));
59 // FTPConn::~FTPConn - Destructor /*{{{*/
60 // ---------------------------------------------------------------------
67 // FTPConn::Close - Close down the connection /*{{{*/
68 // ---------------------------------------------------------------------
69 /* Just tear down the socket and data socket */
78 memset(&PasvAddr
,0,sizeof(PasvAddr
));
81 // FTPConn::Open - Open a new connection /*{{{*/
82 // ---------------------------------------------------------------------
83 /* Connect to the server using a non-blocking connection and perform a
85 bool FTPConn::Open(pkgAcqMethod
*Owner
)
87 // Use the already open connection if possible.
93 // Determine the proxy setting
94 if (getenv("ftp_proxy") == 0)
96 string DefProxy
= _config
->Find("Acquire::ftp::Proxy");
97 string SpecificProxy
= _config
->Find("Acquire::ftp::Proxy::" + ServerName
.Host
);
98 if (SpecificProxy
.empty() == false)
100 if (SpecificProxy
== "DIRECT")
103 Proxy
= SpecificProxy
;
109 Proxy
= getenv("ftp_proxy");
111 // Determine what host and port to use based on the proxy settings
114 if (Proxy
.empty() == true)
116 if (ServerName
.Port
!= 0)
117 Port
= ServerName
.Port
;
118 Host
= ServerName
.Host
;
127 // Connect to the remote server
128 if (Connect(Host
,Port
,"ftp",21,ServerFd
,TimeOut
,Owner
) == false)
130 socklen_t Len
= sizeof(Peer
);
131 if (getpeername(ServerFd
,(sockaddr
*)&Peer
,&Len
) != 0)
132 return _error
->Errno("getpeername","Unable to determine the peer name");
134 Owner
->Status("Logging in");
138 // FTPConn::Login - Login to the remote server /*{{{*/
139 // ---------------------------------------------------------------------
140 /* This performs both normal login and proxy login using a simples script
141 stored in the config file. */
142 bool FTPConn::Login()
147 // Setup the variables needed for authentication
148 string User
= "anonymous";
149 string Pass
= "apt_get_ftp_2.0@debian.linux.user";
151 // Fill in the user/pass
152 if (ServerName
.User
.empty() == false)
153 User
= ServerName
.User
;
154 if (ServerName
.Password
.empty() == false)
155 Pass
= ServerName
.Password
;
157 // Perform simple login
158 if (Proxy
.empty() == true)
160 // Read the initial response
161 if (ReadResp(Tag
,Msg
) == false)
164 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
167 if (WriteMsg(Tag
,Msg
,"USER %s",User
.c_str()) == false)
170 return _error
->Error("USER failed, server said: %s",Msg
.c_str());
173 if (WriteMsg(Tag
,Msg
,"PASS %s",Pass
.c_str()) == false)
176 return _error
->Error("PASS failed, server said: %s",Msg
.c_str());
178 // Enter passive mode
179 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
180 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
182 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
186 // Read the initial response
187 if (ReadResp(Tag
,Msg
) == false)
190 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
192 // Perform proxy script execution
193 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
194 if (Opts
== 0 || Opts
->Child
== 0)
195 return _error
->Error("A proxy server was specified but no login "
196 "script, Acquire::ftp::ProxyLogin is empty.");
199 // Iterate over the entire login script
200 for (; Opts
!= 0; Opts
= Opts
->Next
)
202 if (Opts
->Value
.empty() == true)
205 // Substitute the variables into the command
207 if (ServerName
.Port
!= 0)
208 sprintf(SitePort
,"%u",ServerName
.Port
);
210 strcpy(SitePort
,"21");
211 string Tmp
= Opts
->Value
;
212 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
213 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
214 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
215 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
216 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
217 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
220 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
223 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
226 // Enter passive mode
228 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
229 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
232 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true)
233 TryPassive
= _config
->FindB("Acquire::FTP::Proxy::Passive",true);
235 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
240 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
243 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
248 // FTPConn::ReadLine - Read a line from the server /*{{{*/
249 // ---------------------------------------------------------------------
250 /* This performs a very simple buffered read. */
251 bool FTPConn::ReadLine(string
&Text
)
257 while (Len
< sizeof(Buffer
))
259 // Scan the buffer for a new line
260 for (unsigned int I
= 0; I
!= Len
; I
++)
262 // Escape some special chars
267 if (Buffer
[I
] != '\n')
271 Text
= string(Buffer
,I
);
272 memmove(Buffer
,Buffer
+I
,Len
- I
);
277 // Wait for some data..
278 if (WaitFd(ServerFd
,false,TimeOut
) == false)
281 return _error
->Error("Connection timeout");
285 int Res
= read(ServerFd
,Buffer
+ Len
,sizeof(Buffer
) - Len
);
288 _error
->Errno("read","Read error");
295 return _error
->Error("A response overflowed the buffer.");
298 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
299 // ---------------------------------------------------------------------
300 /* This reads a reply code from the server, it handles both p */
301 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
303 // Grab the first line of the response
305 if (ReadLine(Msg
) == false)
310 Ret
= strtol(Msg
.c_str(),&End
,10);
311 if (End
- Msg
.c_str() != 3)
312 return _error
->Error("Protocol corruption");
315 Text
= Msg
.c_str()+4;
319 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
324 return _error
->Error("Protocol corruption");
326 /* Okay, here we do the continued message trick. This is foolish, but
327 proftpd follows the protocol as specified and wu-ftpd doesn't, so
328 we filter. I wonder how many clients break if you use proftpd and
329 put a '- in the 3rd spot in the message? */
331 strncpy(Leader
,Msg
.c_str(),3);
333 while (ReadLine(Msg
) == true)
335 // Short, it must be using RFC continuation..
336 if (Msg
.length() < 4)
343 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
345 Text
+= Msg
.c_str()+4;
349 // This message has the wu-ftpd style reply code prefixed
350 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
352 Text
+= Msg
.c_str()+4;
356 // Must be RFC style prefixing
360 if (Debug
== true && _error
->PendingError() == false)
361 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
363 return !_error
->PendingError();
366 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
367 // ---------------------------------------------------------------------
368 /* Simple printf like function.. */
369 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
374 // sprintf the description
376 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
380 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
383 unsigned long Len
= strlen(S
);
384 unsigned long Start
= 0;
387 if (WaitFd(ServerFd
,true,TimeOut
) == false)
390 return _error
->Error("Connection timeout");
393 int Res
= write(ServerFd
,S
+ Start
,Len
);
396 _error
->Errno("write","Write Error");
405 return ReadResp(Ret
,Text
);
408 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
409 // ---------------------------------------------------------------------
410 /* Try to enter passive mode, the return code does not indicate if passive
411 mode could or could not be established, only if there was a fatal error.
412 Borrowed mostly from lftp. We have to enter passive mode every time
413 we make a data connection :| */
414 bool FTPConn::GoPasv()
416 // Try to enable pasv mode
419 if (WriteMsg(Tag
,Msg
,"PASV") == false)
422 // Unsupported function
423 string::size_type Pos
= Msg
.find('(');
424 if (Tag
>= 400 || Pos
== string::npos
)
426 memset(&PasvAddr
,0,sizeof(PasvAddr
));
431 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
432 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
434 memset(&PasvAddr
,0,sizeof(PasvAddr
));
438 // lftp used this horrid byte order manipulation.. Ik.
439 PasvAddr
.sin_family
= AF_INET
;
442 a
= (unsigned char *)&PasvAddr
.sin_addr
;
443 p
= (unsigned char *)&PasvAddr
.sin_port
;
445 // Some evil servers return 0 to mean their addr
446 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
448 PasvAddr
.sin_addr
= Peer
.sin_addr
;
464 // FTPConn::Size - Return the size of a file /*{{{*/
465 // ---------------------------------------------------------------------
466 /* Grab the file size from the server, 0 means no size or empty file */
467 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
473 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
477 Size
= strtol(Msg
.c_str(),&End
,10);
478 if (Tag
>= 400 || End
== Msg
.c_str())
483 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
484 // ---------------------------------------------------------------------
485 /* Like Size no error is returned if the command is not supported. If the
486 command fails then time is set to the current time of day to fool
488 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
492 // Query the mod time
495 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
497 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
505 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
506 // ---------------------------------------------------------------------
507 /* Create the data connection. Call FinalizeDataFd after this though.. */
508 bool FTPConn::CreateDataFd()
513 // Attempt to enter passive mode.
514 if (TryPassive
== true)
516 if (GoPasv() == false)
519 // Oops, didn't work out, don't bother trying again.
520 if (PasvAddr
.sin_port
== 0)
525 if (PasvAddr
.sin_port
!= 0)
528 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
529 return _error
->Errno("socket","Could not create a socket");
531 // Connect to the server
532 SetNonBlock(DataFd
,true);
533 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
534 errno
!= EINPROGRESS
)
535 return _error
->Errno("socket","Could not create a socket");
537 /* This implements a timeout for connect by opening the connection
539 if (WaitFd(DataFd
,true,TimeOut
) == false)
540 return _error
->Error("Could not connect data socket, connection timed out");
542 unsigned int Len
= sizeof(Err
);
543 if (getsockopt(DataFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
544 return _error
->Errno("getsockopt","Failed");
546 return _error
->Error("Could not connect.");
556 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
557 return _error
->Errno("socket","Could not create a socket");
561 memset(&Addr
,0,sizeof(Addr
));
562 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
563 return _error
->Errno("bind","Could not bind a socket");
564 if (listen(DataListenFd
,1) < 0)
565 return _error
->Errno("listen","Could not listen on the socket");
566 SetNonBlock(DataListenFd
,true);
568 // Determine the name to send to the remote
570 socklen_t Jnk
= sizeof(Addr
);
571 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
572 return _error
->Errno("getsockname","Could not determine the socket's name");
574 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
575 return _error
->Errno("getsockname","Could not determine the socket's name");
577 // This bit ripped from qftp
578 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
579 unsigned long bp
= ntohs(Addr
.sin_port
);
581 // Send the port command
584 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
585 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
586 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
587 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
590 return _error
->Error("Unable to send port command");
595 // FTPConn::Finalize - Complete the Data connection /*{{{*/
596 // ---------------------------------------------------------------------
597 /* If the connection is in port mode this waits for the other end to hook
599 bool FTPConn::Finalize()
601 // Passive mode? Do nothing
602 if (PasvAddr
.sin_port
!= 0)
605 // Close any old socket..
609 // Wait for someone to connect..
610 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
611 return _error
->Error("Data socket connect timed out");
613 // Accept the connection
614 struct sockaddr_in Addr
;
615 socklen_t Len
= sizeof(Addr
);
616 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
618 return _error
->Errno("accept","Unable to accept connection");
626 // FTPConn::Get - Get a file /*{{{*/
627 // ---------------------------------------------------------------------
628 /* This opens a data connection, sends REST and RETR and then
629 transfers the file over. */
630 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
631 MD5Summation
&MD5
,bool &Missing
)
634 if (CreateDataFd() == false)
641 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
647 if (To
.Truncate(Resume
) == false)
650 if (To
.Seek(0) == false)
655 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
657 _error
->Errno("read","Problem hashing file");
662 // Send the get command
663 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
670 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
673 // Finish off the data connection
674 if (Finalize() == false)
678 unsigned char Buffer
[4096];
681 // Wait for some data..
682 if (WaitFd(DataFd
,false,TimeOut
) == false)
685 return _error
->Error("Data socket timed out");
689 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
700 if (To
.Write(Buffer
,Res
) == false)
711 // Read the closing message from the server
712 if (ReadResp(Tag
,Msg
) == false)
715 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
720 // FtpMethod::FtpMethod - Constructor /*{{{*/
721 // ---------------------------------------------------------------------
723 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
725 signal(SIGTERM
,SigTerm
);
726 signal(SIGINT
,SigTerm
);
732 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
733 // ---------------------------------------------------------------------
734 /* This closes and timestamps the open file. This is neccessary to get
735 resume behavoir on user abort */
736 void FtpMethod::SigTerm(int)
744 UBuf
.actime
= FailTime
;
745 UBuf
.modtime
= FailTime
;
746 utime(FailFile
.c_str(),&UBuf
);
751 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
752 // ---------------------------------------------------------------------
753 /* We stash the desired pipeline depth */
754 bool FtpMethod::Configuration(string Message
)
756 if (pkgAcqMethod::Configuration(Message
) == false)
759 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
763 // FtpMethod::Fetch - Fetch a file /*{{{*/
764 // ---------------------------------------------------------------------
765 /* Fetch a single file, called by the base class.. */
766 bool FtpMethod::Fetch(FetchItem
*Itm
)
769 const char *File
= Get
.Path
.c_str();
771 Res
.Filename
= Itm
->DestFile
;
774 // Connect to the server
775 if (Server
== 0 || Server
->Comp(Get
) == false)
778 Server
= new FTPConn(Get
);
781 // Could not connect is a transient error..
782 if (Server
->Open(this) == false)
789 // Get the files information
792 if (Server
->Size(File
,Size
) == false ||
793 Server
->ModTime(File
,FailTime
) == false)
800 // See if it is an IMS hit
801 if (Itm
->LastModified
== FailTime
)
809 // See if the file exists
811 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
813 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
815 Res
.Size
= Buf
.st_size
;
816 Res
.LastModified
= Buf
.st_mtime
;
817 Res
.ResumePoint
= Buf
.st_size
;
823 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
824 Res
.ResumePoint
= Buf
.st_size
;
830 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
831 if (_error
->PendingError() == true)
836 FailFile
= Itm
->DestFile
;
837 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
841 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
847 UBuf
.actime
= FailTime
;
848 UBuf
.modtime
= FailTime
;
849 utime(FailFile
.c_str(),&UBuf
);
851 // If the file is missing we hard fail otherwise transient fail
858 Res
.Size
= Fd
.Size();
861 Res
.LastModified
= FailTime
;
862 Res
.MD5Sum
= MD5
.Result();
866 UBuf
.actime
= FailTime
;
867 UBuf
.modtime
= FailTime
;
868 utime(Queue
->DestFile
.c_str(),&UBuf
);
877 int main(int argc
,const char *argv
[])
879 /* See if we should be come the http client - we do this for http
881 if (getenv("ftp_proxy") != 0)
883 URI Proxy
= string(getenv("ftp_proxy"));
884 if (Proxy
.Access
== "http")
886 // Copy over the environment setting
888 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
891 // Run the http method
892 string Path
= flNotFile(argv
[0]) + "/http";
893 execl(Path
.c_str(),Path
.c_str(),0);
894 cerr
<< "Unable to invoke " << Path
<< endl
;