]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.16 1999/11/29 23:20:27 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
);
289 return _error
->Errno("read","Read error");
294 return _error
->Error("A response overflowed the buffer.");
297 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
298 // ---------------------------------------------------------------------
299 /* This reads a reply code from the server, it handles both p */
300 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
302 // Grab the first line of the response
304 if (ReadLine(Msg
) == false)
309 Ret
= strtol(Msg
.c_str(),&End
,10);
310 if (End
- Msg
.c_str() != 3)
311 return _error
->Error("Protocol corruption");
314 Text
= Msg
.c_str()+4;
318 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
323 return _error
->Error("Protocol corruption");
325 /* Okay, here we do the continued message trick. This is foolish, but
326 proftpd follows the protocol as specified and wu-ftpd doesn't, so
327 we filter. I wonder how many clients break if you use proftpd and
328 put a '- in the 3rd spot in the message? */
330 strncpy(Leader
,Msg
.c_str(),3);
332 while (ReadLine(Msg
) == true)
334 // Short, it must be using RFC continuation..
335 if (Msg
.length() < 4)
342 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
344 Text
+= Msg
.c_str()+4;
348 // This message has the wu-ftpd style reply code prefixed
349 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
351 Text
+= Msg
.c_str()+4;
355 // Must be RFC style prefixing
359 if (Debug
== true && _error
->PendingError() == false)
360 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
362 return !_error
->PendingError();
365 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
366 // ---------------------------------------------------------------------
367 /* Simple printf like function.. */
368 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
373 // sprintf the description
375 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
379 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
382 unsigned long Len
= strlen(S
);
383 unsigned long Start
= 0;
386 if (WaitFd(ServerFd
,true,TimeOut
) == false)
389 return _error
->Error("Connection timeout");
392 int Res
= write(ServerFd
,S
+ Start
,Len
);
396 return _error
->Errno("write","Write Error");
403 return ReadResp(Ret
,Text
);
406 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
407 // ---------------------------------------------------------------------
408 /* Try to enter passive mode, the return code does not indicate if passive
409 mode could or could not be established, only if there was a fatal error.
410 Borrowed mostly from lftp. We have to enter passive mode every time
411 we make a data connection :| */
412 bool FTPConn::GoPasv()
414 // Try to enable pasv mode
417 if (WriteMsg(Tag
,Msg
,"PASV") == false)
420 // Unsupported function
421 string::size_type Pos
= Msg
.find('(');
422 if (Tag
>= 400 || Pos
== string::npos
)
424 memset(&PasvAddr
,0,sizeof(PasvAddr
));
429 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
430 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
432 memset(&PasvAddr
,0,sizeof(PasvAddr
));
436 // lftp used this horrid byte order manipulation.. Ik.
437 PasvAddr
.sin_family
= AF_INET
;
440 a
= (unsigned char *)&PasvAddr
.sin_addr
;
441 p
= (unsigned char *)&PasvAddr
.sin_port
;
443 // Some evil servers return 0 to mean their addr
444 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
446 PasvAddr
.sin_addr
= Peer
.sin_addr
;
462 // FTPConn::Size - Return the size of a file /*{{{*/
463 // ---------------------------------------------------------------------
464 /* Grab the file size from the server, 0 means no size or empty file */
465 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
471 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
475 Size
= strtol(Msg
.c_str(),&End
,10);
476 if (Tag
>= 400 || End
== Msg
.c_str())
481 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
482 // ---------------------------------------------------------------------
483 /* Like Size no error is returned if the command is not supported. If the
484 command fails then time is set to the current time of day to fool
486 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
490 // Query the mod time
493 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
495 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
500 memset(&tm
,0,sizeof(tm
));
501 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
502 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
508 /* We use timegm from the GNU C library, libapt-pkg will provide this
509 symbol if it does not exist */
514 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
515 // ---------------------------------------------------------------------
516 /* Create the data connection. Call FinalizeDataFd after this though.. */
517 bool FTPConn::CreateDataFd()
522 // Attempt to enter passive mode.
523 if (TryPassive
== true)
525 if (GoPasv() == false)
528 // Oops, didn't work out, don't bother trying again.
529 if (PasvAddr
.sin_port
== 0)
534 if (PasvAddr
.sin_port
!= 0)
537 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
538 return _error
->Errno("socket","Could not create a socket");
540 // Connect to the server
541 SetNonBlock(DataFd
,true);
542 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
543 errno
!= EINPROGRESS
)
544 return _error
->Errno("socket","Could not create a socket");
546 /* This implements a timeout for connect by opening the connection
548 if (WaitFd(ServerFd
,true,TimeOut
) == false)
549 return _error
->Error("Could not connect data socket, connection timed out");
551 unsigned int Len
= sizeof(Err
);
552 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
553 return _error
->Errno("getsockopt","Failed");
555 return _error
->Error("Could not connect.");
565 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
566 return _error
->Errno("socket","Could not create a socket");
570 memset(&Addr
,0,sizeof(Addr
));
571 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
572 return _error
->Errno("bind","Could not bind a socket");
573 if (listen(DataListenFd
,1) < 0)
574 return _error
->Errno("listen","Could not listen on the socket");
575 SetNonBlock(DataListenFd
,true);
577 // Determine the name to send to the remote
579 socklen_t Jnk
= sizeof(Addr
);
580 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
581 return _error
->Errno("getsockname","Could not determine the socket's name");
583 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
584 return _error
->Errno("getsockname","Could not determine the socket's name");
586 // This bit ripped from qftp
587 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
588 unsigned long bp
= ntohs(Addr
.sin_port
);
590 // Send the port command
593 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
594 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
595 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
596 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
599 return _error
->Error("Unable to send port command");
604 // FTPConn::Finalize - Complete the Data connection /*{{{*/
605 // ---------------------------------------------------------------------
606 /* If the connection is in port mode this waits for the other end to hook
608 bool FTPConn::Finalize()
610 // Passive mode? Do nothing
611 if (PasvAddr
.sin_port
!= 0)
614 // Close any old socket..
618 // Wait for someone to connect..
619 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
620 return _error
->Error("Data socket connect timed out");
622 // Accept the connection
623 struct sockaddr_in Addr
;
624 socklen_t Len
= sizeof(Addr
);
625 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
627 return _error
->Errno("accept","Unable to accept connection");
635 // FTPConn::Get - Get a file /*{{{*/
636 // ---------------------------------------------------------------------
637 /* This opens a data connection, sends REST and RETR and then
638 transfers the file over. */
639 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
640 MD5Summation
&MD5
,bool &Missing
)
643 if (CreateDataFd() == false)
650 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
656 if (To
.Truncate(Resume
) == false)
659 if (To
.Seek(0) == false)
664 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
666 _error
->Errno("read","Problem hashing file");
671 // Send the get command
672 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
679 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
682 // Finish off the data connection
683 if (Finalize() == false)
687 unsigned char Buffer
[4096];
690 // Wait for some data..
691 if (WaitFd(DataFd
,false,TimeOut
) == false)
694 return _error
->Error("Data socket timed out");
698 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
709 if (To
.Write(Buffer
,Res
) == false)
720 // Read the closing message from the server
721 if (ReadResp(Tag
,Msg
) == false)
724 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
729 // FtpMethod::FtpMethod - Constructor /*{{{*/
730 // ---------------------------------------------------------------------
732 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
734 signal(SIGTERM
,SigTerm
);
735 signal(SIGINT
,SigTerm
);
741 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
742 // ---------------------------------------------------------------------
743 /* This closes and timestamps the open file. This is neccessary to get
744 resume behavoir on user abort */
745 void FtpMethod::SigTerm(int)
753 UBuf
.actime
= FailTime
;
754 UBuf
.modtime
= FailTime
;
755 utime(FailFile
.c_str(),&UBuf
);
760 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
761 // ---------------------------------------------------------------------
762 /* We stash the desired pipeline depth */
763 bool FtpMethod::Configuration(string Message
)
765 if (pkgAcqMethod::Configuration(Message
) == false)
768 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
772 // FtpMethod::Fetch - Fetch a file /*{{{*/
773 // ---------------------------------------------------------------------
774 /* Fetch a single file, called by the base class.. */
775 bool FtpMethod::Fetch(FetchItem
*Itm
)
778 const char *File
= Get
.Path
.c_str();
780 Res
.Filename
= Itm
->DestFile
;
783 // Connect to the server
784 if (Server
== 0 || Server
->Comp(Get
) == false)
787 Server
= new FTPConn(Get
);
790 // Could not connect is a transient error..
791 if (Server
->Open(this) == false)
798 // Get the files information
801 if (Server
->Size(File
,Size
) == false ||
802 Server
->ModTime(File
,FailTime
) == false)
809 // See if it is an IMS hit
810 if (Itm
->LastModified
== FailTime
)
818 // See if the file exists
820 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
822 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
824 Res
.Size
= Buf
.st_size
;
825 Res
.LastModified
= Buf
.st_mtime
;
831 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
832 Res
.ResumePoint
= Buf
.st_size
;
838 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
839 if (_error
->PendingError() == true)
844 FailFile
= Itm
->DestFile
;
845 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
849 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
856 UBuf
.actime
= FailTime
;
857 UBuf
.modtime
= FailTime
;
858 utime(FailFile
.c_str(),&UBuf
);
860 // If the file is missing we hard fail otherwise transient fail
867 Res
.Size
= Fd
.Size();
870 Res
.LastModified
= FailTime
;
871 Res
.MD5Sum
= MD5
.Result();
876 UBuf
.actime
= FailTime
;
877 UBuf
.modtime
= FailTime
;
878 utime(Queue
->DestFile
.c_str(),&UBuf
);
887 int main(int argc
,const char *argv
[])
889 /* See if we should be come the http client - we do this for http
891 if (getenv("ftp_proxy") != 0)
893 URI Proxy
= string(getenv("ftp_proxy"));
894 if (Proxy
.Access
== "http")
896 // Copy over the environment setting
898 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
901 // Run the http method
902 string Path
= flNotFile(argv
[0]) + "/http";
903 execl(Path
.c_str(),Path
.c_str(),0);
904 cerr
<< "Unable to invoke " << Path
<< endl
;