]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.9 1999/05/23 23:30:09 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)
730 return _error
->Error("Data socket timed out");
734 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
745 if (To
.Write(Buffer
,Res
) == false)
756 // Read the closing message from the server
757 if (ReadResp(Tag
,Msg
) == false)
760 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
765 // FtpMethod::FtpMethod - Constructor /*{{{*/
766 // ---------------------------------------------------------------------
768 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
770 signal(SIGTERM
,SigTerm
);
771 signal(SIGINT
,SigTerm
);
777 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
778 // ---------------------------------------------------------------------
779 /* This closes and timestamps the open file. This is neccessary to get
780 resume behavoir on user abort */
781 void FtpMethod::SigTerm(int)
789 UBuf
.actime
= FailTime
;
790 UBuf
.modtime
= FailTime
;
791 utime(FailFile
.c_str(),&UBuf
);
796 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
797 // ---------------------------------------------------------------------
798 /* We stash the desired pipeline depth */
799 bool FtpMethod::Configuration(string Message
)
801 if (pkgAcqMethod::Configuration(Message
) == false)
804 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
808 // FtpMethod::Fetch - Fetch a file /*{{{*/
809 // ---------------------------------------------------------------------
810 /* Fetch a single file, called by the base class.. */
811 bool FtpMethod::Fetch(FetchItem
*Itm
)
814 const char *File
= Get
.Path
.c_str();
816 Res
.Filename
= Itm
->DestFile
;
819 // Connect to the server
820 if (Server
== 0 || Server
->Comp(Get
) == false)
823 Server
= new FTPConn(Get
);
826 // Could not connect is a transient error..
827 if (Server
->Open(this) == false)
833 // Get the files information
836 if (Server
->Size(File
,Size
) == false ||
837 Server
->ModTime(File
,FailTime
) == false)
844 // See if it is an IMS hit
845 if (Itm
->LastModified
== FailTime
)
853 // See if the file exists
855 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
857 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
859 Res
.Size
= Buf
.st_size
;
860 Res
.LastModified
= Buf
.st_mtime
;
866 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
867 Res
.ResumePoint
= Buf
.st_size
;
873 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
874 if (_error
->PendingError() == true)
879 FailFile
= Itm
->DestFile
;
880 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
884 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
886 // If the file is missing we hard fail otherwise transient fail
893 Res
.Size
= Fd
.Size();
896 Res
.LastModified
= FailTime
;
897 Res
.MD5Sum
= MD5
.Result();
902 UBuf
.actime
= FailTime
;
903 UBuf
.modtime
= FailTime
;
904 utime(Queue
->DestFile
.c_str(),&UBuf
);
913 int main(int argc
,const char *argv
[])
915 /* See if we should be come the http client - we do this for http
917 if (getenv("ftp_proxy") != 0)
919 URI Proxy
= string(getenv("ftp_proxy"));
920 if (Proxy
.Access
== "http")
922 // Copy over the environment setting
924 snprintf(S
,sizeof(S
),"http_proxy=%s",getenv("ftp_proxy"));
927 // Run the http method
928 string Path
= flNotFile(argv
[0]) + "/http";
929 execl(Path
.c_str(),Path
.c_str(),0);
930 cerr
<< "Unable to invoke " << Path
<< endl
;