]>
git.saurik.com Git - apt.git/blob - methods/ftp.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: ftp.cc,v 1.1 1999/03/15 06:00:59 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>
32 #include <netinet/in.h>
33 #include <sys/socket.h>
34 #include <arpa/inet.h>
39 unsigned long TimeOut
= 120;
43 // FTPConn::FTPConn - Constructor /*{{{*/
44 // ---------------------------------------------------------------------
46 FTPConn::FTPConn(URI Srv
) : Len(0), ServerFd(-1), DataFd(-1),
47 DataListenFd(-1), ServerName(Srv
)
50 memset(&PasvAddr
,0,sizeof(PasvAddr
));
53 // FTPConn::~FTPConn - Destructor /*{{{*/
54 // ---------------------------------------------------------------------
61 // FTPConn::Close - Close down the connection /*{{{*/
62 // ---------------------------------------------------------------------
63 /* Just tear down the socket and data socket */
72 memset(&PasvAddr
,0,sizeof(PasvAddr
));
75 // FTPConn::Open - Open a new connection /*{{{*/
76 // ---------------------------------------------------------------------
77 /* Connect to the server using a non-blocking connection and perform a
83 // Use the already open connection if possible.
89 // Determine the proxy setting
90 if (getenv("ftp_proxy") == 0)
92 string DefProxy
= _config
->Find("Acquire::ftp::Proxy");
93 string SpecificProxy
= _config
->Find("Acquire::ftp::Proxy::" + ServerName
.Host
);
94 if (SpecificProxy
.empty() == false)
96 if (SpecificProxy
== "DIRECT")
99 Proxy
= SpecificProxy
;
105 Proxy
= getenv("ftp_proxy");
107 // Determine what host and port to use based on the proxy settings
110 if (Proxy
.empty() == true)
112 if (ServerName
.Port
!= 0)
113 Port
= ServerName
.Port
;
115 ServerName
.Port
= Port
;
116 Host
= ServerName
.Host
;
125 /* We used a cached address record.. Yes this is against the spec but
126 the way we have setup our rotating dns suggests that this is more
128 if (LastHost
!= Host
)
130 // Owner->Status("Connecting to %s",Host.c_str());
133 hostent
*Addr
= gethostbyname(Host
.c_str());
134 if (Addr
== 0 || Addr
->h_addr_list
[0] == 0)
135 return _error
->Error("Could not resolve '%s'",Host
.c_str());
137 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
140 // Owner->Status("Connecting to %s (%s)",Host.c_str(),inet_ntoa(LastHostA));
143 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
144 return _error
->Errno("socket","Could not create a socket");
146 // Connect to the server
147 struct sockaddr_in server
;
148 server
.sin_family
= AF_INET
;
149 server
.sin_port
= htons(Port
);
150 server
.sin_addr
= LastHostA
;
151 SetNonBlock(ServerFd
,true);
152 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0 &&
153 errno
!= EINPROGRESS
)
154 return _error
->Errno("socket","Could not create a socket");
157 /* This implements a timeout for connect by opening the connection
159 if (WaitFd(ServerFd
,true,TimeOut
) == false)
160 return _error
->Error("Could not connect, connection timed out");
162 unsigned int Len
= sizeof(Err
);
163 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
164 return _error
->Errno("getsockopt","Failed");
166 return _error
->Error("Could not connect.");
171 // FTPConn::Login - Login to the remote server /*{{{*/
172 // ---------------------------------------------------------------------
173 /* This performs both normal login and proxy login using a simples script
174 stored in the config file. */
175 bool FTPConn::Login()
180 // Setup the variables needed for authentication
181 string User
= "anonymous";
182 string Pass
= "apt_get_ftp_2.0@debian.linux.user";
184 // Fill in the user/pass
185 if (ServerName
.User
.empty() == false)
186 User
= ServerName
.User
;
187 if (ServerName
.Password
.empty() == false)
188 Pass
= ServerName
.Password
;
190 // Perform simple login
191 if (Proxy
.empty() == true)
193 // Read the initial response
194 if (ReadResp(Tag
,Msg
) == false)
197 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
200 if (WriteMsg(Tag
,Msg
,"USER %s",User
.c_str()) == false)
203 return _error
->Error("USER failed, server said: %s",Msg
.c_str());
206 if (WriteMsg(Tag
,Msg
,"PASS %s",Pass
.c_str()) == false)
209 return _error
->Error("PASS failed, server said: %s",Msg
.c_str());
211 // Enter passive mode
213 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true &&
214 _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true) == true)
220 if (_config
->FindB("Acquire::FTP::Passive",true) == true)
226 // Read the initial response
227 if (ReadResp(Tag
,Msg
) == false)
230 return _error
->Error("Server refused our connection and said: %s",Msg
.c_str());
232 // Perform proxy script execution
233 Configuration::Item
const *Opts
= _config
->Tree("Acquire::ftp::ProxyLogin");
234 if (Opts
== 0 || Opts
->Child
== 0)
235 return _error
->Error("A proxy server was specified but no login "
236 "script, Acquire::ftp::ProxyLogin is empty.");
239 // Iterate over the entire login script
240 for (; Opts
!= 0; Opts
= Opts
->Next
)
242 if (Opts
->Value
.empty() == true)
245 // Substitute the variables into the command
247 sprintf(SitePort
,"%u",ServerName
.Port
);
248 string Tmp
= Opts
->Value
;
249 Tmp
= SubstVar(Tmp
,"$(PROXY_USER)",Proxy
.User
);
250 Tmp
= SubstVar(Tmp
,"$(PROXY_PASS)",Proxy
.Password
);
251 Tmp
= SubstVar(Tmp
,"$(SITE_USER)",User
);
252 Tmp
= SubstVar(Tmp
,"$(SITE_PASS)",Pass
);
253 Tmp
= SubstVar(Tmp
,"$(SITE_PORT)",SitePort
);
254 Tmp
= SubstVar(Tmp
,"$(SITE)",ServerName
.Host
);
257 if (WriteMsg(Tag
,Msg
,"%s",Tmp
.c_str()) == false)
260 return _error
->Error("Login script command '%s' failed, server said: %s",Tmp
.c_str(),Msg
.c_str());
263 // Enter passive mode
265 if (_config
->Exists("Acquire::FTP::Passive::" + ServerName
.Host
) == true &&
266 _config
->FindB("Acquire::FTP::Passive::" + ServerName
.Host
,true) == true)
272 if (_config
->Exists("Acquire::FTP::Proxy::Passive") == true &&
273 _config
->FindB("Acquire::FTP::Proxy::Passive",true) == true)
276 if (_config
->FindB("Acquire::FTP::Passive",true) == true)
282 if (WriteMsg(Tag
,Msg
,"TYPE I") == false)
285 return _error
->Error("TYPE failed, server said: %s",Msg
.c_str());
290 // FTPConn::ReadLine - Read a line from the server /*{{{*/
291 // ---------------------------------------------------------------------
292 /* This performs a very simple buffered read. */
293 bool FTPConn::ReadLine(string
&Text
)
296 while (Len
< sizeof(Buffer
))
298 // Scan the buffer for a new line
299 for (unsigned int I
= 0; I
!= Len
; I
++)
301 // Escape some special chars
306 if (Buffer
[I
] != '\n')
310 Text
= string(Buffer
,I
);
311 memmove(Buffer
,Buffer
+I
,Len
- I
);
316 // Wait for some data..
317 if (WaitFd(ServerFd
,false,TimeOut
) == false)
318 return _error
->Error("Connection timeout");
321 int Res
= read(ServerFd
,Buffer
,sizeof(Buffer
) - Len
);
323 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 cout
<< "<- '" << 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 cout
<< "<- '" << 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 cout
<< "-> '" << QuoteString(S
,"") << "'" << endl
;
415 unsigned long Len
= strlen(S
);
416 unsigned long Start
= 0;
419 if (WaitFd(ServerFd
,true,TimeOut
) == false)
420 return _error
->Error("Connection timeout");
422 int Res
= write(ServerFd
,S
+ Start
,Len
);
424 return _error
->Errno("write","Write Error");
429 return ReadResp(Ret
,Text
);
432 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
433 // ---------------------------------------------------------------------
434 /* Try to enter passive mode, the return code does not indicate if passive
435 mode could or could not be established, only if there was a fatal error.
436 Borrowed mostly from lftp. We have to enter passive mode every time
437 we make a data connection :| */
438 bool FTPConn::GoPasv()
440 // Try to enable pasv mode
443 if (WriteMsg(Tag
,Msg
,"PASV") == false)
446 // Unsupported function
447 string::size_type Pos
= Msg
.find('(');
448 if (Tag
>= 400 || Pos
== string::npos
)
450 memset(&PasvAddr
,0,sizeof(PasvAddr
));
455 unsigned a0
,a1
,a2
,a3
,p0
,p1
;
456 if (sscanf(Msg
.c_str() + Pos
,"(%u,%u,%u,%u,%u,%u)",&a0
,&a1
,&a2
,&a3
,&p0
,&p1
) != 6)
458 memset(&PasvAddr
,0,sizeof(PasvAddr
));
462 // lftp used this horrid byte order manipulation.. Ik.
463 PasvAddr
.sin_family
= AF_INET
;
466 a
= (unsigned char *)&PasvAddr
.sin_addr
;
467 p
= (unsigned char *)&PasvAddr
.sin_port
;
469 // Some evil servers return 0 to mean their addr
470 if (a0
== 0 && a1
== 0 && a2
== 0 && a3
== 0)
472 PasvAddr
.sin_addr
= Peer
.sin_addr
;
488 // FTPConn::Size - Return the size of a file /*{{{*/
489 // ---------------------------------------------------------------------
490 /* Grab the file size from the server, 0 means no size or empty file */
491 unsigned long FTPConn::Size(const char *Path
)
496 if (WriteMsg(Tag
,Msg
,"SIZE %s",Path
) == false)
500 unsigned long Size
= strtol(Msg
.c_str(),&End
,10);
501 if (Tag
>= 400 || End
== Msg
.c_str())
506 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
507 // ---------------------------------------------------------------------
508 /* Like Size no error is returned if the command is not supported. If the
509 command fails then time is set to the current time of day to fool
511 bool FTPConn::ModTime(const char *Path
, time_t &Time
)
515 // Query the mod time
518 if (WriteMsg(Tag
,Msg
,"MDTM %s",Path
) == false)
520 if (Tag
>= 400 || Msg
.empty() == true || isdigit(Msg
[0]) == 0)
525 memset(&tm
,0,sizeof(tm
));
526 if (sscanf(Msg
.c_str(),"%4d%2d%2d%2d%2d%2d",&tm
.tm_year
,&tm
.tm_mon
,
527 &tm
.tm_mday
,&tm
.tm_hour
,&tm
.tm_min
,&tm
.tm_sec
) != 6)
533 /* We use timegm from the GNU C library, libapt-pkg will provide this
534 symbol if it does not exist */
539 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
540 // ---------------------------------------------------------------------
541 /* Create the data connection. Call FinalizeDataFd after this though.. */
542 bool FTPConn::CreateDataFd()
547 // Attempt to enter passive mode.
548 if (TryPassive
== true)
550 if (GoPasv() == false)
553 // Oops, didn't work out, don't bother trying again.
554 if (PasvAddr
.sin_port
== 0)
559 if (PasvAddr
.sin_port
!= 0)
562 if ((DataFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
563 return _error
->Errno("socket","Could not create a socket");
565 // Connect to the server
566 SetNonBlock(DataFd
,true);
567 if (connect(DataFd
,(sockaddr
*)&PasvAddr
,sizeof(PasvAddr
)) < 0 &&
568 errno
!= EINPROGRESS
)
569 return _error
->Errno("socket","Could not create a socket");
571 /* This implements a timeout for connect by opening the connection
573 if (WaitFd(ServerFd
,true,TimeOut
) == false)
574 return _error
->Error("Could not connect data socket, connection timed out");
576 unsigned int Len
= sizeof(Err
);
577 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
578 return _error
->Errno("getsockopt","Failed");
580 return _error
->Error("Could not connect.");
586 if (DataListenFd
== -1)
589 if ((DataListenFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
590 return _error
->Errno("socket","Could not create a socket");
594 memset(&Addr
,0,sizeof(Addr
));
595 if (bind(DataListenFd
,(sockaddr
*)&Addr
,sizeof(Addr
)) < 0)
596 return _error
->Errno("bind","Could not bind a socket");
597 if (listen(DataListenFd
,1) < 0)
598 return _error
->Errno("listen","Could not listen on the socket");
599 SetNonBlock(DataListenFd
,true);
602 // Determine the name to send to the remote
605 socklen_t Jnk
= sizeof(Addr
);
606 if (getsockname(DataListenFd
,(sockaddr
*)&Addr
,&Jnk
) < 0)
607 return _error
->Errno("getsockname","Could not determine the socket's name");
609 if (getsockname(ServerFd
,(sockaddr
*)&Addr2
,&Jnk
) < 0)
610 return _error
->Errno("getsockname","Could not determine the socket's name");
612 // This bit ripped from qftp
613 unsigned long badr
= ntohl(*(unsigned long *)&Addr2
.sin_addr
);
614 unsigned long bp
= ntohs(Addr
.sin_port
);
616 // Send the port command
619 if (WriteMsg(Tag
,Msg
,"PORT %d,%d,%d,%d,%d,%d",
620 (int) (badr
>> 24) & 0xff, (int) (badr
>> 16) & 0xff,
621 (int) (badr
>> 8) & 0xff, (int) badr
& 0xff,
622 (int) (bp
>> 8) & 0xff, (int) bp
& 0xff) == false)
625 return _error
->Error("Unable to send port command");
630 // FTPConn::Finalize - Complete the Data connection /*{{{*/
631 // ---------------------------------------------------------------------
632 /* If the connection is in port mode this waits for the other end to hook
634 bool FTPConn::Finalize()
636 // Passive mode? Do nothing
637 if (PasvAddr
.sin_port
!= 0)
640 // Close any old socket..
644 // Wait for someone to connect..
645 if (WaitFd(DataListenFd
,false,TimeOut
) == false)
646 return _error
->Error("Data socket connect timed out");
648 // Accept the connection
649 struct sockaddr_in Addr
;
650 socklen_t Len
= sizeof(Addr
);
651 DataFd
= accept(DataListenFd
,(struct sockaddr
*)&Addr
,&Len
);
653 return _error
->Errno("accept","Unable to accept connection");
658 // FTPConn::Get - Get a file /*{{{*/
659 // ---------------------------------------------------------------------
660 /* This opens a data connection, sends REST and RETR and then
661 transfers the file over. */
662 bool FTPConn::Get(const char *Path
,FileFd
&To
,unsigned long Resume
)
664 if (CreateDataFd() == false)
671 if (WriteMsg(Tag
,Msg
,"REST %u",Resume
) == false)
677 if (To
.Truncate(Resume
) == false)
680 // Send the get command
681 if (WriteMsg(Tag
,Msg
,"RETR %s",Path
) == false)
685 return _error
->Error("Unable to fetch file, server said '%s'",Msg
.c_str());
687 // Finish off the data connection
688 if (Finalize() == false)
692 unsigned char Buffer
[4096];
695 // Wait for some data..
696 if (WaitFd(DataFd
,false,TimeOut
) == false)
697 return _error
->Error("Data socket connect timed out");
700 int Res
= read(DataFd
,Buffer
,sizeof(Buffer
));
710 if (To
.Write(Buffer
,Res
) == false)
718 // Read the closing message from the server
719 if (ReadResp(Tag
,Msg
) == false)
722 return _error
->Error("Data transfer failed, server said '%s'",Msg
.c_str());
729 FTPConn
Con(URI("ftp://va.debian.org/debian/README"));
731 _config
->Set("Acquire::FTP::Passive","false");
735 if (Con
.Open() == false)
737 cout
<< "Size: " << Con
.Size("/debian/README") << endl
;
740 Con
.ModTime("/debian/README",Time
);
741 cout
<< "Time: " << TimeRFC1123(Time
) << endl
;
745 FileFd
F("t",FileFd::WriteEmpty
);
746 Con
.Get("/debian/README",F
);
751 FileFd
F("t3",FileFd::WriteEmpty
);
752 Con
.Get("/debian/README.pgp",F
);
758 _error
->DumpErrors();