]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.6 1999/04/11 21:23:10 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
215 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
216 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
218 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
222 // Read the initial response
223 if (ReadResp(Tag
,Msg
) == false)
226 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
228 // Perform proxy script execution
229 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
230 if (Opts
== 0 || Opts
->Child
== 0)
231 return _error
->Error("A proxy server was specified but no login "
232 "script, Acquire::ftp::ProxyLogin is empty.");
235 // Iterate over the entire login script
236 for (; Opts
!= 0; Opts
= Opts
->Next
)
238 if (Opts
->Value
.empty() == true)
241 // Substitute the variables into the command
243 if (ServerName
.Port
!= 0)
244 sprintf(SitePort
,"%u",ServerName
.Port
);
246 strcpy(SitePort
,"21");
247 string Tmp
= Opts
->Value
;
248 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
249 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
250 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
251 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
252 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
253 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
256 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
259 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
262 // Enter passive mode
264 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true)
265 TryPassive
= _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true);
268 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true)
269 TryPassive
= _config
->FindB("Acquire::FTP::Proxy::Passive",true);
271 TryPassive
= _config
->FindB("Acquire::FTP::Passive",true);
276 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
279 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
284 // FTPConn::ReadLine - Read a line from the server /*{{{*/
285 // ---------------------------------------------------------------------
286 /* This performs a very simple buffered read. */
287 bool FTPConn::ReadLine(string
&Text
)
293 while (Len
< sizeof(Buffer
))
295 // Scan the buffer for a new line
296 for (unsigned int I
= 0; I
!= Len
; I
++)
298 // Escape some special chars
303 if (Buffer
[I
] != '\n')
307 Text
= string(Buffer
,I
);
308 memmove(Buffer
,Buffer
+I
,Len
- I
);
313 // Wait for some data..
314 if (WaitFd(ServerFd
,false,TimeOut
) == false)
317 return _error
->Error("Connection timeout");
321 int Res
= read(ServerFd
,Buffer
+ Len
,sizeof(Buffer
) - Len
);
325 return _error
->Errno("read","Read error");
330 return _error
->Error("A response overflowed the buffer.");
333 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
334 // ---------------------------------------------------------------------
335 /* This reads a reply code from the server, it handles both p */
336 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
338 // Grab the first line of the response
340 if (ReadLine(Msg
) == false)
345 Ret
= strtol(Msg
.c_str(),&End
,10);
346 if (End
- Msg
.c_str() != 3)
347 return _error
->Error("Protocol corruption");
350 Text
= Msg
.c_str()+4;
354 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
359 return _error
->Error("Protocol corruption");
361 /* Okay, here we do the continued message trick. This is foolish, but
362 proftpd follows the protocol as specified and wu-ftpd doesn't, so
363 we filter. I wonder how many clients break if you use proftpd and
364 put a '- in the 3rd spot in the message? */
366 strncpy(Leader
,Msg
.c_str(),3);
368 while (ReadLine(Msg
) == true)
370 // Short, it must be using RFC continuation..
371 if (Msg
.length() < 4)
378 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
380 Text
+= Msg
.c_str()+4;
384 // This message has the wu-ftpd style reply code prefixed
385 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
387 Text
+= Msg
.c_str()+4;
391 // Must be RFC style prefixing
395 if (Debug
== true && _error
->PendingError() == false)
396 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
398 return !_error
->PendingError();
401 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
402 // ---------------------------------------------------------------------
403 /* Simple printf like function.. */
404 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
409 // sprintf the description
411 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
415 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
418 unsigned long Len
= strlen(S
);
419 unsigned long Start
= 0;
422 if (WaitFd(ServerFd
,true,TimeOut
) == false)
425 return _error
->Error("Connection timeout");
428 int Res
= write(ServerFd
,S
+ Start
,Len
);
432 return _error
->Errno("write","Write Error");
439 return ReadResp(Ret
,Text
);
442 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
443 // ---------------------------------------------------------------------
444 /* Try to enter passive mode, the return code does not indicate if passive
445 mode could or could not be established, only if there was a fatal error.
446 Borrowed mostly from lftp. We have to enter passive mode every time
447 we make a data connection :| */
448 bool FTPConn::GoPasv()
450 // Try to enable pasv mode
453 if (WriteMsg(Tag
,Msg
,"PASV") == false)
456 // Unsupported function
457 string::size_type Pos
= Msg
.find('(');
458 if (Tag
>= 400 || Pos
== string::npos
)
460 memset(&PasvAddr
,0,sizeof(PasvAddr
));
465 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
466 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
468 memset(&PasvAddr
,0,sizeof(PasvAddr
));
472 // lftp used this horrid byte order manipulation.. Ik.
473 PasvAddr
.sin_family
= AF_INET
;
476 a
= (unsigned char *)&PasvAddr
.sin_addr
;
477 p
= (unsigned char *)&PasvAddr
.sin_port
;
479 // Some evil servers return 0 to mean their addr
480 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
482 PasvAddr
.sin_addr
= Peer
.sin_addr
;
498 // FTPConn::Size - Return the size of a file /*{{{*/
499 // ---------------------------------------------------------------------
500 /* Grab the file size from the server, 0 means no size or empty file */
501 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
507 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
511 Size
= strtol(Msg
.c_str(),&End
,10);
512 if (Tag
>= 400 || End
== Msg
.c_str())
517 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
518 // ---------------------------------------------------------------------
519 /* Like Size no error is returned if the command is not supported. If the
520 command fails then time is set to the current time of day to fool
522 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
526 // Query the mod time
529 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
531 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
536 memset(&tm
,0,sizeof(tm
));
537 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
538 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
544 /* We use timegm from the GNU C library, libapt-pkg will provide this
545 symbol if it does not exist */
550 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
551 // ---------------------------------------------------------------------
552 /* Create the data connection. Call FinalizeDataFd after this though.. */
553 bool FTPConn::CreateDataFd()
558 // Attempt to enter passive mode.
559 if (TryPassive
== true)
561 if (GoPasv() == false)
564 // Oops, didn't work out, don't bother trying again.
565 if (PasvAddr
.sin_port
== 0)
570 if (PasvAddr
.sin_port
!= 0)
573 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
574 return _error
->Errno("socket","Could not create a socket");
576 // Connect to the server
577 SetNonBlock(DataFd
,true);
578 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
579 errno
!= EINPROGRESS
)
580 return _error
->Errno("socket","Could not create a socket");
582 /* This implements a timeout for connect by opening the connection
584 if (WaitFd(ServerFd
,true,TimeOut
) == false)
585 return _error
->Error("Could not connect data socket, connection timed out");
587 unsigned int Len
= sizeof(Err
);
588 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
589 return _error
->Errno("getsockopt","Failed");
591 return _error
->Error("Could not connect.");
601 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
602 return _error
->Errno("socket","Could not create a socket");
606 memset(&Addr
,0,sizeof(Addr
));
607 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
608 return _error
->Errno("bind","Could not bind a socket");
609 if (listen(DataListenFd
,1) < 0)
610 return _error
->Errno("listen","Could not listen on the socket");
611 SetNonBlock(DataListenFd
,true);
613 // Determine the name to send to the remote
615 socklen_t Jnk
= sizeof(Addr
);
616 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
617 return _error
->Errno("getsockname","Could not determine the socket's name");
619 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
620 return _error
->Errno("getsockname","Could not determine the socket's name");
622 // This bit ripped from qftp
623 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
624 unsigned long bp
= ntohs(Addr
.sin_port
);
626 // Send the port command
629 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
630 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
631 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
632 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
635 return _error
->Error("Unable to send port command");
640 // FTPConn::Finalize - Complete the Data connection /*{{{*/
641 // ---------------------------------------------------------------------
642 /* If the connection is in port mode this waits for the other end to hook
644 bool FTPConn::Finalize()
646 // Passive mode? Do nothing
647 if (PasvAddr
.sin_port
!= 0)
650 // Close any old socket..
654 // Wait for someone to connect..
655 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
656 return _error
->Error("Data socket connect timed out");
658 // Accept the connection
659 struct sockaddr_in Addr
;
660 socklen_t Len
= sizeof(Addr
);
661 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
663 return _error
->Errno("accept","Unable to accept connection");
671 // FTPConn::Get - Get a file /*{{{*/
672 // ---------------------------------------------------------------------
673 /* This opens a data connection, sends REST and RETR and then
674 transfers the file over. */
675 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
676 MD5Summation
&MD5
,bool &Missing
)
679 if (CreateDataFd() == false)
686 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
692 if (To
.Truncate(Resume
) == false)
695 if (To
.Seek(0) == false)
700 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
702 _error
->Errno("read","Problem hashing file");
707 // Send the get command
708 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
715 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
718 // Finish off the data connection
719 if (Finalize() == false)
723 unsigned char Buffer
[4096];
726 // Wait for some data..
727 if (WaitFd(DataFd
,false,TimeOut
) == false)
728 return _error
->Error("Data socket connect timed out");
731 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
742 if (To
.Write(Buffer
,Res
) == false)
750 // Read the closing message from the server
751 if (ReadResp(Tag
,Msg
) == false)
754 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
759 // FtpMethod::FtpMethod - Constructor /*{{{*/
760 // ---------------------------------------------------------------------
762 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
764 signal(SIGTERM
,SigTerm
);
765 signal(SIGINT
,SigTerm
);
771 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
772 // ---------------------------------------------------------------------
773 /* This closes and timestamps the open file. This is neccessary to get
774 resume behavoir on user abort */
775 void FtpMethod::SigTerm(int)
783 UBuf
.actime
= FailTime
;
784 UBuf
.modtime
= FailTime
;
785 utime(FailFile
.c_str(),&UBuf
);
790 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
791 // ---------------------------------------------------------------------
792 /* We stash the desired pipeline depth */
793 bool FtpMethod::Configuration(string Message
)
795 if (pkgAcqMethod::Configuration(Message
) == false)
798 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
802 // FtpMethod::Fetch - Fetch a file /*{{{*/
803 // ---------------------------------------------------------------------
804 /* Fetch a single file, called by the base class.. */
805 bool FtpMethod::Fetch(FetchItem
*Itm
)
808 const char *File
= Get
.Path
.c_str();
810 Res
.Filename
= Itm
->DestFile
;
813 // Connect to the server
814 if (Server
== 0 || Server
->Comp(Get
) == false)
817 Server
= new FTPConn(Get
);
820 // Could not connect is a transient error..
821 if (Server
->Open(this) == false)
827 // Get the files information
830 if (Server
->Size(File
,Size
) == false ||
831 Server
->ModTime(File
,FailTime
) == false)
838 // See if it is an IMS hit
839 if (Itm
->LastModified
== FailTime
)
847 // See if the file exists
849 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
851 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
853 Res
.Size
= Buf
.st_size
;
854 Res
.LastModified
= Buf
.st_mtime
;
860 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
861 Res
.ResumePoint
= Buf
.st_size
;
867 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
868 if (_error
->PendingError() == true)
873 FailFile
= Itm
->DestFile
;
874 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
878 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
880 // If the file is missing we hard fail otherwise transient fail
887 Res
.Size
= Fd
.Size();
890 Res
.LastModified
= FailTime
;
891 Res
.MD5Sum
= MD5
.Result();
896 UBuf
.actime
= FailTime
;
897 UBuf
.modtime
= FailTime
;
898 utime(Queue
->DestFile
.c_str(),&UBuf
);