1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.5 1999/03/15 19:51: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 ##################################################################### */
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
)
290 while (Len
< sizeof(Buffer
))
292 // Scan the buffer for a new line
293 for (unsigned int I
= 0; I
!= Len
; I
++)
295 // Escape some special chars
300 if (Buffer
[I
] != '\n')
304 Text
= string(Buffer
,I
);
305 memmove(Buffer
,Buffer
+I
,Len
- I
);
310 // Wait for some data..
311 if (WaitFd(ServerFd
,false,TimeOut
) == false)
314 return _error
->Error("Connection timeout");
318 int Res
= read(ServerFd
,Buffer
,sizeof(Buffer
) - Len
);
322 return _error
->Errno("read","Read error");
327 return _error
->Error("A response overflowed the buffer.");
330 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
331 // ---------------------------------------------------------------------
332 /* This reads a reply code from the server, it handles both p */
333 bool FTPConn::ReadResp(unsigned int &Ret
,string
&Text
)
335 // Grab the first line of the response
337 if (ReadLine(Msg
) == false)
342 Ret
= strtol(Msg
.c_str(),&End
,10);
343 if (End
- Msg
.c_str() != 3)
344 return _error
->Error("Protocol corruption");
347 Text
= Msg
.c_str()+4;
351 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
356 return _error
->Error("Protocol corruption");
358 /* Okay, here we do the continued message trick. This is foolish, but
359 proftpd follows the protocol as specified and wu-ftpd doesn't, so
360 we filter. I wonder how many clients break if you use proftpd and
361 put a '- in the 3rd spot in the message? */
363 strncpy(Leader
,Msg
.c_str(),3);
365 while (ReadLine(Msg
) == true)
367 // Short, it must be using RFC continuation..
368 if (Msg
.length() < 4)
375 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == ' ')
377 Text
+= Msg
.c_str()+4;
381 // This message has the wu-ftpd style reply code prefixed
382 if (strncmp(Msg
.c_str(),Leader
,3) == 0 && Msg
[3] == '-')
384 Text
+= Msg
.c_str()+4;
388 // Must be RFC style prefixing
392 if (Debug
== true && _error
->PendingError() == false)
393 cerr
<< "<- '" << QuoteString(Text
,"") << "'" << endl
;
395 return !_error
->PendingError();
398 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
399 // ---------------------------------------------------------------------
400 /* Simple printf like function.. */
401 bool FTPConn::WriteMsg(unsigned int &Ret
,string
&Text
,const char *Fmt
,...)
406 // sprintf the description
408 vsnprintf(S
,sizeof(S
) - 4,Fmt
,args
);
412 cerr
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
415 unsigned long Len
= strlen(S
);
416 unsigned long Start
= 0;
419 if (WaitFd(ServerFd
,true,TimeOut
) == false)
422 return _error
->Error("Connection timeout");
425 int Res
= write(ServerFd
,S
+ Start
,Len
);
429 return _error
->Errno("write","Write Error");
436 return ReadResp(Ret
,Text
);
439 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
440 // ---------------------------------------------------------------------
441 /* Try to enter passive mode, the return code does not indicate if passive
442 mode could or could not be established, only if there was a fatal error.
443 Borrowed mostly from lftp. We have to enter passive mode every time
444 we make a data connection :| */
445 bool FTPConn::GoPasv()
447 // Try to enable pasv mode
450 if (WriteMsg(Tag
,Msg
,"PASV") == false)
453 // Unsupported function
454 string::size_type Pos
= Msg
.find('(');
455 if (Tag
>= 400 || Pos
== string::npos
)
457 memset(&PasvAddr
,0,sizeof(PasvAddr
));
462 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
463 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
465 memset(&PasvAddr
,0,sizeof(PasvAddr
));
469 // lftp used this horrid byte order manipulation.. Ik.
470 PasvAddr
.sin_family
= AF_INET
;
473 a
= (unsigned char *)&PasvAddr
.sin_addr
;
474 p
= (unsigned char *)&PasvAddr
.sin_port
;
476 // Some evil servers return 0 to mean their addr
477 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
479 PasvAddr
.sin_addr
= Peer
.sin_addr
;
495 // FTPConn::Size - Return the size of a file /*{{{*/
496 // ---------------------------------------------------------------------
497 /* Grab the file size from the server, 0 means no size or empty file */
498 bool FTPConn::Size(const char *Path
,unsigned long &Size
)
504 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
508 Size
= strtol(Msg
.c_str(),&End
,10);
509 if (Tag
>= 400 || End
== Msg
.c_str())
514 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
515 // ---------------------------------------------------------------------
516 /* Like Size no error is returned if the command is not supported. If the
517 command fails then time is set to the current time of day to fool
519 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
523 // Query the mod time
526 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
528 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
533 memset(&tm
,0,sizeof(tm
));
534 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
535 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
541 /* We use timegm from the GNU C library, libapt-pkg will provide this
542 symbol if it does not exist */
547 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
548 // ---------------------------------------------------------------------
549 /* Create the data connection. Call FinalizeDataFd after this though.. */
550 bool FTPConn::CreateDataFd()
555 // Attempt to enter passive mode.
556 if (TryPassive
== true)
558 if (GoPasv() == false)
561 // Oops, didn't work out, don't bother trying again.
562 if (PasvAddr
.sin_port
== 0)
567 if (PasvAddr
.sin_port
!= 0)
570 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
571 return _error
->Errno("socket","Could not create a socket");
573 // Connect to the server
574 SetNonBlock(DataFd
,true);
575 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
576 errno
!= EINPROGRESS
)
577 return _error
->Errno("socket","Could not create a socket");
579 /* This implements a timeout for connect by opening the connection
581 if (WaitFd(ServerFd
,true,TimeOut
) == false)
582 return _error
->Error("Could not connect data socket, connection timed out");
584 unsigned int Len
= sizeof(Err
);
585 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
586 return _error
->Errno("getsockopt","Failed");
588 return _error
->Error("Could not connect.");
598 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
599 return _error
->Errno("socket","Could not create a socket");
603 memset(&Addr
,0,sizeof(Addr
));
604 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
605 return _error
->Errno("bind","Could not bind a socket");
606 if (listen(DataListenFd
,1) < 0)
607 return _error
->Errno("listen","Could not listen on the socket");
608 SetNonBlock(DataListenFd
,true);
610 // Determine the name to send to the remote
612 socklen_t Jnk
= sizeof(Addr
);
613 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
614 return _error
->Errno("getsockname","Could not determine the socket's name");
616 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
617 return _error
->Errno("getsockname","Could not determine the socket's name");
619 // This bit ripped from qftp
620 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
621 unsigned long bp
= ntohs(Addr
.sin_port
);
623 // Send the port command
626 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
627 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
628 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
629 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
632 return _error
->Error("Unable to send port command");
637 // FTPConn::Finalize - Complete the Data connection /*{{{*/
638 // ---------------------------------------------------------------------
639 /* If the connection is in port mode this waits for the other end to hook
641 bool FTPConn::Finalize()
643 // Passive mode? Do nothing
644 if (PasvAddr
.sin_port
!= 0)
647 // Close any old socket..
651 // Wait for someone to connect..
652 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
653 return _error
->Error("Data socket connect timed out");
655 // Accept the connection
656 struct sockaddr_in Addr
;
657 socklen_t Len
= sizeof(Addr
);
658 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
660 return _error
->Errno("accept","Unable to accept connection");
668 // FTPConn::Get - Get a file /*{{{*/
669 // ---------------------------------------------------------------------
670 /* This opens a data connection, sends REST and RETR and then
671 transfers the file over. */
672 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
673 MD5Summation
&MD5
,bool &Missing
)
676 if (CreateDataFd() == false)
683 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
689 if (To
.Truncate(Resume
) == false)
692 if (To
.Seek(0) == false)
697 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
699 _error
->Errno("read","Problem hashing file");
704 // Send the get command
705 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
712 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
715 // Finish off the data connection
716 if (Finalize() == false)
720 unsigned char Buffer
[4096];
723 // Wait for some data..
724 if (WaitFd(DataFd
,false,TimeOut
) == false)
725 return _error
->Error("Data socket connect timed out");
728 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
739 if (To
.Write(Buffer
,Res
) == false)
747 // Read the closing message from the server
748 if (ReadResp(Tag
,Msg
) == false)
751 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
756 // FtpMethod::FtpMethod - Constructor /*{{{*/
757 // ---------------------------------------------------------------------
759 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
761 signal(SIGTERM
,SigTerm
);
762 signal(SIGINT
,SigTerm
);
768 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
769 // ---------------------------------------------------------------------
770 /* This closes and timestamps the open file. This is neccessary to get
771 resume behavoir on user abort */
772 void FtpMethod::SigTerm(int)
780 UBuf
.actime
= FailTime
;
781 UBuf
.modtime
= FailTime
;
782 utime(FailFile
.c_str(),&UBuf
);
787 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
788 // ---------------------------------------------------------------------
789 /* We stash the desired pipeline depth */
790 bool FtpMethod::Configuration(string Message
)
792 if (pkgAcqMethod::Configuration(Message
) == false)
795 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
799 // FtpMethod::Fetch - Fetch a file /*{{{*/
800 // ---------------------------------------------------------------------
801 /* Fetch a single file, called by the base class.. */
802 bool FtpMethod::Fetch(FetchItem
*Itm
)
805 const char *File
= Get
.Path
.c_str();
807 Res
.Filename
= Itm
->DestFile
;
810 // Connect to the server
811 if (Server
== 0 || Server
->Comp(Get
) == false)
814 Server
= new FTPConn(Get
);
817 // Could not connect is a transient error..
818 if (Server
->Open(this) == false)
824 // Get the files information
827 if (Server
->Size(File
,Size
) == false ||
828 Server
->ModTime(File
,FailTime
) == false)
835 // See if it is an IMS hit
836 if (Itm
->LastModified
== FailTime
)
844 // See if the file exists
846 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
848 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
850 Res
.Size
= Buf
.st_size
;
851 Res
.LastModified
= Buf
.st_mtime
;
857 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
858 Res
.ResumePoint
= Buf
.st_size
;
864 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
865 if (_error
->PendingError() == true)
870 FailFile
= Itm
->DestFile
;
871 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
875 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
877 // If the file is missing we hard fail otherwise transient fail
884 Res
.Size
= Fd
.Size();
887 Res
.LastModified
= FailTime
;
888 Res
.MD5Sum
= MD5
.Result();
893 UBuf
.actime
= FailTime
;
894 UBuf
.modtime
= FailTime
;
895 utime(Queue
->DestFile
.c_str(),&UBuf
);