]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
a7fa8323303bb924608b8f29fe7ff4bff610877c
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.17 1999/12/09 03:45:56 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)
502 memset(&tm
,0,sizeof(tm
));
503 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
504 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
510 /* We use timegm from the GNU C library, libapt-pkg will provide this
511 symbol if it does not exist */
516 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
517 // ---------------------------------------------------------------------
518 /* Create the data connection. Call FinalizeDataFd after this though.. */
519 bool FTPConn::CreateDataFd()
524 // Attempt to enter passive mode.
525 if (TryPassive
== true)
527 if (GoPasv() == false)
530 // Oops, didn't work out, don't bother trying again.
531 if (PasvAddr
.sin_port
== 0)
536 if (PasvAddr
.sin_port
!= 0)
539 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
540 return _error
->Errno("socket","Could not create a socket");
542 // Connect to the server
543 SetNonBlock(DataFd
,true);
544 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
545 errno
!= EINPROGRESS
)
546 return _error
->Errno("socket","Could not create a socket");
548 /* This implements a timeout for connect by opening the connection
550 if (WaitFd(ServerFd
,true,TimeOut
) == false)
551 return _error
->Error("Could not connect data socket, connection timed out");
553 unsigned int Len
= sizeof(Err
);
554 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
555 return _error
->Errno("getsockopt","Failed");
557 return _error
->Error("Could not connect.");
567 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
568 return _error
->Errno("socket","Could not create a socket");
572 memset(&Addr
,0,sizeof(Addr
));
573 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
574 return _error
->Errno("bind","Could not bind a socket");
575 if (listen(DataListenFd
,1) < 0)
576 return _error
->Errno("listen","Could not listen on the socket");
577 SetNonBlock(DataListenFd
,true);
579 // Determine the name to send to the remote
581 socklen_t Jnk
= sizeof(Addr
);
582 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
583 return _error
->Errno("getsockname","Could not determine the socket's name");
585 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
586 return _error
->Errno("getsockname","Could not determine the socket's name");
588 // This bit ripped from qftp
589 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
590 unsigned long bp
= ntohs(Addr
.sin_port
);
592 // Send the port command
595 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
596 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
597 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
598 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
601 return _error
->Error("Unable to send port command");
606 // FTPConn::Finalize - Complete the Data connection /*{{{*/
607 // ---------------------------------------------------------------------
608 /* If the connection is in port mode this waits for the other end to hook
610 bool FTPConn::Finalize()
612 // Passive mode? Do nothing
613 if (PasvAddr
.sin_port
!= 0)
616 // Close any old socket..
620 // Wait for someone to connect..
621 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
622 return _error
->Error("Data socket connect timed out");
624 // Accept the connection
625 struct sockaddr_in Addr
;
626 socklen_t Len
= sizeof(Addr
);
627 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
629 return _error
->Errno("accept","Unable to accept connection");
637 // FTPConn::Get - Get a file /*{{{*/
638 // ---------------------------------------------------------------------
639 /* This opens a data connection, sends REST and RETR and then
640 transfers the file over. */
641 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
642 MD5Summation
&MD5
,bool &Missing
)
645 if (CreateDataFd() == false)
652 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
658 if (To
.Truncate(Resume
) == false)
661 if (To
.Seek(0) == false)
666 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
668 _error
->Errno("read","Problem hashing file");
673 // Send the get command
674 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
681 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
684 // Finish off the data connection
685 if (Finalize() == false)
689 unsigned char Buffer
[4096];
692 // Wait for some data..
693 if (WaitFd(DataFd
,false,TimeOut
) == false)
696 return _error
->Error("Data socket timed out");
700 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
711 if (To
.Write(Buffer
,Res
) == false)
722 // Read the closing message from the server
723 if (ReadResp(Tag
,Msg
) == false)
726 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
731 // FtpMethod::FtpMethod - Constructor /*{{{*/
732 // ---------------------------------------------------------------------
734 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
736 signal(SIGTERM
,SigTerm
);
737 signal(SIGINT
,SigTerm
);
743 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
744 // ---------------------------------------------------------------------
745 /* This closes and timestamps the open file. This is neccessary to get
746 resume behavoir on user abort */
747 void FtpMethod::SigTerm(int)
755 UBuf
.actime
= FailTime
;
756 UBuf
.modtime
= FailTime
;
757 utime(FailFile
.c_str(),&UBuf
);
762 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
763 // ---------------------------------------------------------------------
764 /* We stash the desired pipeline depth */
765 bool FtpMethod::Configuration(string Message
)
767 if (pkgAcqMethod::Configuration(Message
) == false)
770 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
774 // FtpMethod::Fetch - Fetch a file /*{{{*/
775 // ---------------------------------------------------------------------
776 /* Fetch a single file, called by the base class.. */
777 bool FtpMethod::Fetch(FetchItem
*Itm
)
780 const char *File
= Get
.Path
.c_str();
782 Res
.Filename
= Itm
->DestFile
;
785 // Connect to the server
786 if (Server
== 0 || Server
->Comp(Get
) == false)
789 Server
= new FTPConn(Get
);
792 // Could not connect is a transient error..
793 if (Server
->Open(this) == false)
800 // Get the files information
803 if (Server
->Size(File
,Size
) == false ||
804 Server
->ModTime(File
,FailTime
) == false)
811 // See if it is an IMS hit
812 if (Itm
->LastModified
== FailTime
)
820 // See if the file exists
822 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
824 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
826 Res
.Size
= Buf
.st_size
;
827 Res
.LastModified
= Buf
.st_mtime
;
833 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
834 Res
.ResumePoint
= Buf
.st_size
;
840 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
841 if (_error
->PendingError() == true)
846 FailFile
= Itm
->DestFile
;
847 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
851 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
858 UBuf
.actime
= FailTime
;
859 UBuf
.modtime
= FailTime
;
860 utime(FailFile
.c_str(),&UBuf
);
862 // If the file is missing we hard fail otherwise transient fail
869 Res
.Size
= Fd
.Size();
872 Res
.LastModified
= FailTime
;
873 Res
.MD5Sum
= MD5
.Result();
878 UBuf
.actime
= FailTime
;
879 UBuf
.modtime
= FailTime
;
880 utime(Queue
->DestFile
.c_str(),&UBuf
);
889 int main(int argc
,const char *argv
[])
891 /* See if we should be come the http client - we do this for http
893 if (getenv("ftp_proxy") != 0)
895 URI Proxy
= string(getenv("ftp_proxy"));
896 if (Proxy
.Access
== "http")
898 // Copy over the environment setting
900 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
903 // Run the http method
904 string Path
= flNotFile(argv
[0]) + "/http";
905 execl(Path
.c_str(),Path
.c_str(),0);
906 cerr
<< "Unable to invoke " << Path
<< endl
;