]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.3 1999/03/15 08:10:26 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.");
594 if (DataListenFd
== -1)
597 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
598 return _error
->Errno("socket","Could not create a socket");
602 memset(&Addr
,0,sizeof(Addr
));
603 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
604 return _error
->Errno("bind","Could not bind a socket");
605 if (listen(DataListenFd
,1) < 0)
606 return _error
->Errno("listen","Could not listen on the socket");
607 SetNonBlock(DataListenFd
,true);
610 // Determine the name to send to the remote
613 socklen_t Jnk
= sizeof(Addr
);
614 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
615 return _error
->Errno("getsockname","Could not determine the socket's name");
617 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
618 return _error
->Errno("getsockname","Could not determine the socket's name");
620 // This bit ripped from qftp
621 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
622 unsigned long bp
= ntohs(Addr
.sin_port
);
624 // Send the port command
627 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
628 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
629 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
630 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
633 return _error
->Error("Unable to send port command");
638 // FTPConn::Finalize - Complete the Data connection /*{{{*/
639 // ---------------------------------------------------------------------
640 /* If the connection is in port mode this waits for the other end to hook
642 bool FTPConn::Finalize()
644 // Passive mode? Do nothing
645 if (PasvAddr
.sin_port
!= 0)
648 // Close any old socket..
652 // Wait for someone to connect..
653 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
654 return _error
->Error("Data socket connect timed out");
656 // Accept the connection
657 struct sockaddr_in Addr
;
658 socklen_t Len
= sizeof(Addr
);
659 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
661 return _error
->Errno("accept","Unable to accept connection");
666 // FTPConn::Get - Get a file /*{{{*/
667 // ---------------------------------------------------------------------
668 /* This opens a data connection, sends REST and RETR and then
669 transfers the file over. */
670 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
,
671 MD5Summation
&MD5
,bool &Missing
)
674 if (CreateDataFd() == false)
681 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
687 if (To
.Truncate(Resume
) == false)
690 if (To
.Seek(0) == false)
695 if (MD5
.AddFD(To
.Fd(),Resume
) == false)
697 _error
->Errno("read","Problem hashing file");
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
));
737 if (To
.Write(Buffer
,Res
) == false)
745 // Read the closing message from the server
746 if (ReadResp(Tag
,Msg
) == false)
749 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
754 // FtpMethod::FtpMethod - Constructor /*{{{*/
755 // ---------------------------------------------------------------------
757 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig
)
759 signal(SIGTERM
,SigTerm
);
760 signal(SIGINT
,SigTerm
);
766 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
767 // ---------------------------------------------------------------------
768 /* This closes and timestamps the open file. This is neccessary to get
769 resume behavoir on user abort */
770 void FtpMethod::SigTerm(int)
778 UBuf
.actime
= FailTime
;
779 UBuf
.modtime
= FailTime
;
780 utime(FailFile
.c_str(),&UBuf
);
785 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
786 // ---------------------------------------------------------------------
787 /* We stash the desired pipeline depth */
788 bool FtpMethod::Configuration(string Message
)
790 if (pkgAcqMethod::Configuration(Message
) == false)
793 TimeOut
= _config
->FindI("Acquire::Ftp::Timeout",TimeOut
);
797 // FtpMethod::Fetch - Fetch a file /*{{{*/
798 // ---------------------------------------------------------------------
799 /* Fetch a single file, called by the base class.. */
800 bool FtpMethod::Fetch(FetchItem
*Itm
)
803 const char *File
= Get
.Path
.c_str();
805 Res
.Filename
= Itm
->DestFile
;
808 // Connect to the server
809 if (Server
== 0 || Server
->Comp(Get
) == false)
812 Server
= new FTPConn(Get
);
815 // Could not connect is a transient error..
816 if (Server
->Open(this) == false)
822 // Get the files information
824 if (Server
->Size(File
,Size
) == false ||
825 Server
->ModTime(File
,FailTime
) == false)
832 // See if it is an IMS hit
833 if (Itm
->LastModified
== FailTime
)
841 // See if the file exists
843 if (stat(Itm
->DestFile
.c_str(),&Buf
) == 0)
845 if (Size
== (unsigned)Buf
.st_size
&& FailTime
== Buf
.st_mtime
)
847 Res
.Size
= Buf
.st_size
;
848 Res
.LastModified
= Buf
.st_mtime
;
854 if (FailTime
== Buf
.st_mtime
&& Size
> (unsigned)Buf
.st_size
)
855 Res
.ResumePoint
= Buf
.st_size
;
861 FileFd
Fd(Itm
->DestFile
,FileFd::WriteAny
);
862 if (_error
->PendingError() == true)
867 FailFile
= Itm
->DestFile
;
868 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
872 if (Server
->Get(File
,Fd
,Res
.ResumePoint
,MD5
,Missing
) == false)
874 // If the file is missing we hard fail otherwise transient fail
881 Res
.Size
= Fd
.Size();
884 Res
.LastModified
= FailTime
;
885 Res
.MD5Sum
= MD5
.Result();
890 UBuf
.actime
= FailTime
;
891 UBuf
.modtime
= FailTime
;
892 utime(Queue
->DestFile
.c_str(),&UBuf
);