]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.2 1999/03/15 07:20:41 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 ##################################################################### */
15 // Include Files /*{{{*/
16 #include <apt-pkg/fileutl.h>
17 #include <apt-pkg/acquire-method.h>
18 #include <apt-pkg/error.h>
19 #include <apt-pkg/md5.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #include <arpa/inet.h>
41 unsigned long TimeOut
= 120;
43 string
FtpMethod::FailFile
;
44 int FtpMethod::FailFd
= -1;
45 time_t FtpMethod::FailTime
= 0;
47 // FTPConn::FTPConn - Constructor /*{{{*/
48 // ---------------------------------------------------------------------
50 FTPConn::FTPConn(URI Srv
) : Len(0), ServerFd(-1), DataFd(-1),
51 DataListenFd(-1), ServerName(Srv
)
53 Debug
= _config
->FindB("Debug::Acquire::Ftp",false);
54 memset(&PasvAddr
,0,sizeof(PasvAddr
));
57 // FTPConn::~FTPConn - Destructor /*{{{*/
58 // ---------------------------------------------------------------------
65 // FTPConn::Close - Close down the connection /*{{{*/
66 // ---------------------------------------------------------------------
67 /* Just tear down the socket and data socket */
76 memset(&PasvAddr
,0,sizeof(PasvAddr
));
79 // FTPConn::Open - Open a new connection /*{{{*/
80 // ---------------------------------------------------------------------
81 /* 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 /* We used a cached address record.. Yes this is against the spec but
128 the way we have setup our rotating dns suggests that this is more
130 if (LastHost
!= Host
)
132 Owner
->Status("Connecting to %s",Host
.c_str());
135 hostent
*Addr
= gethostbyname(Host
.c_str());
136 if (Addr
== 0 || Addr
->h_addr_list
[0] == 0)
137 return _error
->Error("Could not resolve '%s'",Host
.c_str());
139 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
142 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),inet_ntoa(LastHostA
));
145 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
146 return _error
->Errno("socket","Could not create a socket");
148 // Connect to the server
149 struct sockaddr_in server
;
150 server
.sin_family
= AF_INET
;
151 server
.sin_port
= htons(Port
);
152 server
.sin_addr
= LastHostA
;
153 SetNonBlock(ServerFd
,true);
154 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0 &&
155 errno
!= EINPROGRESS
)
156 return _error
->Errno("socket","Could not create a socket");
159 /* This implements a timeout for connect by opening the connection
161 if (WaitFd(ServerFd
,true,TimeOut
) == false)
162 return _error
->Error("Could not connect, connection timed out");
164 unsigned int Len
= sizeof(Err
);
165 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
166 return _error
->Errno("getsockopt","Failed");
168 return _error
->Error("Could not connect.");
170 Owner
->Status("Logging in");
174 // FTPConn::Login - Login to the remote server /*{{{*/
175 // ---------------------------------------------------------------------
176 /* This performs both normal login and proxy login using a simples script
177 stored in the config file. */
178 bool FTPConn::Login()
183 // Setup the variables needed for authentication
184 string User
= "anonymous";
185 string Pass
= "apt_get_ftp_2.0@debian.linux.user";
187 // Fill in the user/pass
188 if (ServerName
.User
.empty() == false)
189 User
= ServerName
.User
;
190 if (ServerName
.Password
.empty() == false)
191 Pass
= ServerName
.Password
;
193 // Perform simple login
194 if (Proxy
.empty() == true)
196 // Read the initial response
197 if (ReadResp(Tag
,Msg
) == false)
200 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
203 if (WriteMsg(Tag
,Msg
,"USER %s",User
.c_str()) == false)
206 return _error
->Error("USER failed, server said: %s",Msg
.c_str());
209 if (WriteMsg(Tag
,Msg
,"PASS %s",Pass
.c_str()) == false)
212 return _error
->Error("PASS failed, server said: %s",Msg
.c_str());
214 // Enter passive mode
216 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true &&
217 _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true) == true)
223 if (_config
->FindB("Acquire::FTP::Passive",true) == true)
229 // Read the initial response
230 if (ReadResp(Tag
,Msg
) == false)
233 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
235 // Perform proxy script execution
236 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
237 if (Opts
== 0 || Opts
->Child
== 0)
238 return _error
->Error("A proxy server was specified but no login "
239 "script, Acquire::ftp::ProxyLogin is empty.");
242 // Iterate over the entire login script
243 for (; Opts
!= 0; Opts
= Opts
->Next
)
245 if (Opts
->Value
.empty() == true)
248 // Substitute the variables into the command
250 if (ServerName
.Port
!= 0)
251 sprintf(SitePort
,"%u",ServerName
.Port
);
253 strcat("21",SitePort
);
254 string Tmp
= Opts
->Value
;
255 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
256 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
257 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
258 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
259 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
260 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
263 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
266 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
269 // Enter passive mode
271 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true &&
272 _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true) == true)
278 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true &&
279 _config
->FindB("Acquire::FTP::Proxy::Passive",true) == true)
282 if (_config
->FindB("Acquire::FTP::Passive",true) == true)
288 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
291 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
296 // FTPConn::ReadLine - Read a line from the server /*{{{*/
297 // ---------------------------------------------------------------------
298 /* This performs a very simple buffered read. */
299 bool FTPConn::ReadLine(string
&Text
)
302 while (Len
< sizeof(Buffer
))
304 // Scan the buffer for a new line
305 for (unsigned int I
= 0; I
!= Len
; I
++)
307 // Escape some special chars
312 if (Buffer
[I
] != '\n')
316 Text
= string(Buffer
,I
);
317 memmove(Buffer
,Buffer
+I
,Len
- I
);
322 // Wait for some data..
323 if (WaitFd(ServerFd
,false,TimeOut
) == false)
326 return _error
->Error("Connection timeout");
330 int Res
= read(ServerFd
,Buffer
,sizeof(Buffer
) - Len
);
334 return _error
->Errno("read","Read error");
339 return _error
->Error("A response overflowed the buffer.");
342 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
343 // ---------------------------------------------------------------------
344 /* This reads a reply code from the server, it handles both p */
345 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
347 // Grab the first line of the response
349 if (ReadLine(Msg
) == false)
354 Ret
= strtol(Msg
.c_str(),&End
,10);
355 if (End
- Msg
.c_str() != 3)
356 return _error
->Error("Protocol corruption");
359 Text
= Msg
.c_str()+4;
363 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
368 return _error
->Error("Protocol corruption");
370 /* Okay, here we do the continued message trick. This is foolish, but
371 proftpd follows the protocol as specified and wu-ftpd doesn't, so
372 we filter. I wonder how many clients break if you use proftpd and
373 put a '- in the 3rd spot in the message? */
375 strncpy(Leader
,Msg
.c_str(),3);
377 while (ReadLine(Msg
) == true)
379 // Short, it must be using RFC continuation..
380 if (Msg
.length() < 4)
387 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
389 Text
+= Msg
.c_str()+4;
393 // This message has the wu-ftpd style reply code prefixed
394 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
396 Text
+= Msg
.c_str()+4;
400 // Must be RFC style prefixing
404 if (Debug
== true && _error
->PendingError() == false)
405 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
407 return !_error
->PendingError();
410 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
411 // ---------------------------------------------------------------------
412 /* Simple printf like function.. */
413 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
418 // sprintf the description
420 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
424 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
427 unsigned long Len
= strlen(S
);
428 unsigned long Start
= 0;
431 if (WaitFd(ServerFd
,true,TimeOut
) == false)
434 return _error
->Error("Connection timeout");
437 int Res
= write(ServerFd
,S
+ Start
,Len
);
441 return _error
->Errno("write","Write Error");
448 return ReadResp(Ret
,Text
);
451 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
452 // ---------------------------------------------------------------------
453 /* Try to enter passive mode, the return code does not indicate if passive
454 mode could or could not be established, only if there was a fatal error.
455 Borrowed mostly from lftp. We have to enter passive mode every time
456 we make a data connection :| */
457 bool FTPConn::GoPasv()
459 // Try to enable pasv mode
462 if (WriteMsg(Tag
,Msg
,"PASV") == false)
465 // Unsupported function
466 string::size_type Pos
= Msg
.find('(');
467 if (Tag
>= 400 || Pos
== string::npos
)
469 memset(&PasvAddr
,0,sizeof(PasvAddr
));
474 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
475 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
477 memset(&PasvAddr
,0,sizeof(PasvAddr
));
481 // lftp used this horrid byte order manipulation.. Ik.
482 PasvAddr
.sin_family
= AF_INET
;
485 a
= (unsigned char *)&PasvAddr
.sin_addr
;
486 p
= (unsigned char *)&PasvAddr
.sin_port
;
488 // Some evil servers return 0 to mean their addr
489 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
491 PasvAddr
.sin_addr
= Peer
.sin_addr
;
507 // FTPConn::Size - Return the size of a file /*{{{*/
508 // ---------------------------------------------------------------------
509 /* Grab the file size from the server, 0 means no size or empty file */
510 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
516 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
520 Size
= strtol(Msg
.c_str(),&End
,10);
521 if (Tag
>= 400 || End
== Msg
.c_str())
526 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
527 // ---------------------------------------------------------------------
528 /* Like Size no error is returned if the command is not supported. If the
529 command fails then time is set to the current time of day to fool
531 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
535 // Query the mod time
538 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
540 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
545 memset(&tm
,0,sizeof(tm
));
546 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
547 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
553 /* We use timegm from the GNU C library, libapt-pkg will provide this
554 symbol if it does not exist */
559 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
560 // ---------------------------------------------------------------------
561 /* Create the data connection. Call FinalizeDataFd after this though.. */
562 bool FTPConn::CreateDataFd()
567 // Attempt to enter passive mode.
568 if (TryPassive
== true)
570 if (GoPasv() == false)
573 // Oops, didn't work out, don't bother trying again.
574 if (PasvAddr
.sin_port
== 0)
579 if (PasvAddr
.sin_port
!= 0)
582 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
583 return _error
->Errno("socket","Could not create a socket");
585 // Connect to the server
586 SetNonBlock(DataFd
,true);
587 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
588 errno
!= EINPROGRESS
)
589 return _error
->Errno("socket","Could not create a socket");
591 /* This implements a timeout for connect by opening the connection
593 if (WaitFd(ServerFd
,true,TimeOut
) == false)
594 return _error
->Error("Could not connect data socket, connection timed out");
596 unsigned int Len
= sizeof(Err
);
597 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
598 return _error
->Errno("getsockopt","Failed");
600 return _error
->Error("Could not connect.");
606 if (DataListenFd
== -1)
609 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
610 return _error
->Errno("socket","Could not create a socket");
614 memset(&Addr
,0,sizeof(Addr
));
615 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
616 return _error
->Errno("bind","Could not bind a socket");
617 if (listen(DataListenFd
,1) < 0)
618 return _error
->Errno("listen","Could not listen on the socket");
619 SetNonBlock(DataListenFd
,true);
622 // Determine the name to send to the remote
625 socklen_t Jnk
= sizeof(Addr
);
626 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
627 return _error
->Errno("getsockname","Could not determine the socket's name");
629 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
630 return _error
->Errno("getsockname","Could not determine the socket's name");
632 // This bit ripped from qftp
633 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
634 unsigned long bp
= ntohs(Addr
.sin_port
);
636 // Send the port command
639 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
640 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
641 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
642 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
645 return _error
->Error("Unable to send port command");
650 // FTPConn::Finalize - Complete the Data connection /*{{{*/
651 // ---------------------------------------------------------------------
652 /* If the connection is in port mode this waits for the other end to hook
654 bool FTPConn::Finalize()
656 // Passive mode? Do nothing
657 if (PasvAddr
.sin_port
!= 0)
660 // Close any old socket..
664 // Wait for someone to connect..
665 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
666 return _error
->Error("Data socket connect timed out");
668 // Accept the connection
669 struct sockaddr_in Addr
;
670 socklen_t Len
= sizeof(Addr
);
671 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
673 return _error
->Errno("accept","Unable to accept connection");
678 // FTPConn::Get - Get a file /*{{{*/
679 // ---------------------------------------------------------------------
680 /* This opens a data connection, sends REST and RETR and then
681 transfers the file over. */
682 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
683 MD5Summation
&MD5
,bool &Missing
)
686 if (CreateDataFd() == false)
693 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
699 if (To
.Truncate(Resume
) == false)
702 // Send the get command
703 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
710 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
713 // Finish off the data connection
714 if (Finalize() == false)
718 unsigned char Buffer
[4096];
721 // Wait for some data..
722 if (WaitFd(DataFd
,false,TimeOut
) == false)
723 return _error
->Error("Data socket connect timed out");
726 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
736 if (To
.Write(Buffer
,Res
) == false)
744 // Read the closing message from the server
745 if (ReadResp(Tag
,Msg
) == false)
748 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
753 // FtpMethod::FtpMethod - Constructor /*{{{*/
754 // ---------------------------------------------------------------------
756 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
758 signal(SIGTERM
,SigTerm
);
759 signal(SIGINT
,SigTerm
);
765 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
766 // ---------------------------------------------------------------------
767 /* This closes and timestamps the open file. This is neccessary to get
768 resume behavoir on user abort */
769 void FtpMethod::SigTerm(int)
777 UBuf
.actime
= FailTime
;
778 UBuf
.modtime
= FailTime
;
779 utime(FailFile
.c_str(),&UBuf
);
784 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
785 // ---------------------------------------------------------------------
786 /* We stash the desired pipeline depth */
787 bool FtpMethod::Configuration(string Message
)
789 if (pkgAcqMethod::Configuration(Message
) == false)
792 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
796 // FtpMethod::Fetch - Fetch a file /*{{{*/
797 // ---------------------------------------------------------------------
799 bool FtpMethod::Fetch(FetchItem
*Itm
)
802 const char *File
= Get
.Path
.c_str();
804 Res
.Filename
= Itm
->DestFile
;
807 // Connect to the server
808 if (Server
== 0 || Server
->Comp(Get
) == false)
811 Server
= new FTPConn(Get
);
814 // Could not connect is a transient error..
815 if (Server
->Open(this) == false)
821 // Get the files information
823 if (Server
->Size(File
,Size
) == false ||
824 Server
->ModTime(File
,FailTime
) == false)
831 // See if it is an IMS hit
832 if (Itm
->LastModified
== FailTime
)
840 // See if the file exists
842 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
844 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
846 Res
.Size
= Buf
.st_size
;
847 Res
.LastModified
= Buf
.st_mtime
;
853 if (FailTime
== Buf
.st_mtime
&& Size
< (unsigned)Buf
.st_size
)
854 Res
.ResumePoint
= Buf
.st_size
;
860 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
861 if (_error
->PendingError() == true)
866 FailFile
= Itm
->DestFile
;
867 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
871 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
873 // If the file is missing we hard fail otherwise transient fail
880 Res
.Size
= Fd
.Size();
883 Res
.LastModified
= FailTime
;
884 Res
.MD5Sum
= MD5
.Result();
889 UBuf
.actime
= FailTime
;
890 UBuf
.modtime
= FailTime
;
891 utime(Queue
->DestFile
.c_str(),&UBuf
);