]> git.saurik.com Git - apt.git/blame - methods/ftp.cc
zh_CN.po: update simplified chinese translation
[apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
7db98ffc 3// $Id: ftp.cc,v 1.31.2.1 2004/01/16 18:58:50 mdz Exp $
30b30ec1
AL
4/* ######################################################################
5
1e3f4083 6 FTP Acquire Method - This is the FTP acquire method for APT.
30b30ec1
AL
7
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.
934b6582
AL
12
13 RFC 2428 describes the IPv6 FTP behavior
14
30b30ec1
AL
15 ##################################################################### */
16 /*}}}*/
17// Include Files /*{{{*/
ea542140
DK
18#include <config.h>
19
30b30ec1 20#include <apt-pkg/fileutl.h>
30b30ec1 21#include <apt-pkg/error.h>
63b1700f 22#include <apt-pkg/hashes.h>
f1c081b6 23#include <apt-pkg/netrc.h>
472ff00e 24#include <apt-pkg/configuration.h>
453b82a3 25#include <apt-pkg/strutl.h>
30b30ec1 26
453b82a3
DK
27#include <ctype.h>
28#include <stdlib.h>
29#include <string.h>
30b30ec1
AL
30#include <sys/stat.h>
31#include <sys/time.h>
30b30ec1
AL
32#include <unistd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <errno.h>
36#include <stdarg.h>
076cc664 37#include <iostream>
30b30ec1
AL
38
39// Internet stuff
40#include <netinet/in.h>
30b30ec1
AL
41#include <arpa/inet.h>
42#include <netdb.h>
43
6d13bbca 44#include "rfc2553emu.h"
0837bd25 45#include "connect.h"
ce0ae89a 46#include "ftp.h"
453b82a3 47
ea542140 48#include <apti18n.h>
30b30ec1
AL
49 /*}}}*/
50
076cc664
AL
51using namespace std;
52
b2e465d6
AL
53/* This table is for the EPRT and EPSV commands, it maps the OS address
54 family to the IETF address families */
55struct AFMap
56{
57 unsigned long Family;
58 unsigned long IETFFamily;
59};
60
61#ifndef AF_INET6
67c3067f 62struct AFMap AFMap[] = {{AF_INET,1},{0, 0}};
b2e465d6 63#else
67c3067f 64struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{0, 0}};
b2e465d6
AL
65#endif
66
30b30ec1
AL
67unsigned long TimeOut = 120;
68URI Proxy;
ce0ae89a
AL
69string FtpMethod::FailFile;
70int FtpMethod::FailFd = -1;
71time_t FtpMethod::FailTime = 0;
30b30ec1
AL
72
73// FTPConn::FTPConn - Constructor /*{{{*/
74// ---------------------------------------------------------------------
75/* */
25613a61 76FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
f5a34606 77 DataListenFd(-1), ServerName(Srv),
25613a61
DK
78 ForceExtended(false), TryPassive(true),
79 PeerAddrLen(0), ServerAddrLen(0)
30b30ec1 80{
ce0ae89a 81 Debug = _config->FindB("Debug::Acquire::Ftp",false);
b2e465d6 82 PasvAddr = 0;
dcaa1185 83 Buffer[0] = '\0';
30b30ec1
AL
84}
85 /*}}}*/
86// FTPConn::~FTPConn - Destructor /*{{{*/
87// ---------------------------------------------------------------------
88/* */
89FTPConn::~FTPConn()
90{
91 Close();
92}
93 /*}}}*/
94// FTPConn::Close - Close down the connection /*{{{*/
95// ---------------------------------------------------------------------
96/* Just tear down the socket and data socket */
97void FTPConn::Close()
98{
99 close(ServerFd);
100 ServerFd = -1;
101 close(DataFd);
102 DataFd = -1;
103 close(DataListenFd);
104 DataListenFd = -1;
b2e465d6
AL
105
106 if (PasvAddr != 0)
107 freeaddrinfo(PasvAddr);
108 PasvAddr = 0;
30b30ec1
AL
109}
110 /*}}}*/
111// FTPConn::Open - Open a new connection /*{{{*/
112// ---------------------------------------------------------------------
113/* Connect to the server using a non-blocking connection and perform a
114 login. */
ce0ae89a 115bool FTPConn::Open(pkgAcqMethod *Owner)
30b30ec1
AL
116{
117 // Use the already open connection if possible.
118 if (ServerFd != -1)
119 return true;
120
121 Close();
b2e465d6 122
30b30ec1 123 // Determine the proxy setting
788a8f42
EL
124 string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
125 if (!SpecificProxy.empty())
30b30ec1 126 {
788a8f42
EL
127 if (SpecificProxy == "DIRECT")
128 Proxy = "";
129 else
130 Proxy = SpecificProxy;
30b30ec1
AL
131 }
132 else
788a8f42
EL
133 {
134 string DefProxy = _config->Find("Acquire::ftp::Proxy");
135 if (!DefProxy.empty())
136 {
137 Proxy = DefProxy;
138 }
139 else
140 {
141 char* result = getenv("ftp_proxy");
142 Proxy = result ? result : "";
143 }
144 }
145
f8081133
AL
146 // Parse no_proxy, a , separated list of domains
147 if (getenv("no_proxy") != 0)
148 {
149 if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
150 Proxy = "";
151 }
152
30b30ec1 153 // Determine what host and port to use based on the proxy settings
6d13bbca 154 int Port = 0;
30b30ec1
AL
155 string Host;
156 if (Proxy.empty() == true)
157 {
158 if (ServerName.Port != 0)
159 Port = ServerName.Port;
30b30ec1
AL
160 Host = ServerName.Host;
161 }
162 else
163 {
164 if (Proxy.Port != 0)
165 Port = Proxy.Port;
166 Host = Proxy.Host;
167 }
6d13bbca 168
b2e465d6
AL
169 /* Connect to the remote server. Since FTP is connection oriented we
170 want to make sure we get a new server every time we reconnect */
171 RotateDNS();
9505213b 172 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
0837bd25 173 return false;
c968dc2f
AL
174
175 // Login must be before getpeername otherwise dante won't work.
dc738e7a 176 Owner->Status(_("Logging in"));
c968dc2f 177 bool Res = Login();
b2e465d6
AL
178
179 // Get the remote server's address
180 PeerAddrLen = sizeof(PeerAddr);
181 if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
dc738e7a 182 return _error->Errno("getpeername",_("Unable to determine the peer name"));
30b30ec1 183
b2e465d6
AL
184 // Get the local machine's address
185 ServerAddrLen = sizeof(ServerAddr);
186 if (getsockname(ServerFd,(sockaddr *)&ServerAddr,&ServerAddrLen) != 0)
dc738e7a 187 return _error->Errno("getsockname",_("Unable to determine the local name"));
b2e465d6 188
c968dc2f 189 return Res;
30b30ec1
AL
190}
191 /*}}}*/
192// FTPConn::Login - Login to the remote server /*{{{*/
193// ---------------------------------------------------------------------
194/* This performs both normal login and proxy login using a simples script
195 stored in the config file. */
196bool FTPConn::Login()
197{
198 unsigned int Tag;
199 string Msg;
200
201 // Setup the variables needed for authentication
202 string User = "anonymous";
44a38e53 203 string Pass = "apt_get_ftp_2.1@debian.linux.user";
30b30ec1
AL
204
205 // Fill in the user/pass
206 if (ServerName.User.empty() == false)
207 User = ServerName.User;
208 if (ServerName.Password.empty() == false)
209 Pass = ServerName.Password;
210
211 // Perform simple login
212 if (Proxy.empty() == true)
213 {
214 // Read the initial response
215 if (ReadResp(Tag,Msg) == false)
216 return false;
217 if (Tag >= 400)
db0db9fe 218 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
30b30ec1
AL
219
220 // Send the user
221 if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
222 return false;
223 if (Tag >= 400)
dc738e7a 224 return _error->Error(_("USER failed, server said: %s"),Msg.c_str());
30b30ec1 225
2391e7b5
AL
226 if (Tag == 331) { // 331 User name okay, need password.
227 // Send the Password
228 if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
229 return false;
230 if (Tag >= 400)
231 return _error->Error(_("PASS failed, server said: %s"),Msg.c_str());
232 }
30b30ec1
AL
233
234 // Enter passive mode
10861bb5
AL
235 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
236 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1 237 else
b2e465d6 238 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
239 }
240 else
241 {
242 // Read the initial response
243 if (ReadResp(Tag,Msg) == false)
244 return false;
245 if (Tag >= 400)
1169dbfa 246 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
30b30ec1
AL
247
248 // Perform proxy script execution
249 Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
250 if (Opts == 0 || Opts->Child == 0)
dc738e7a
AL
251 return _error->Error(_("A proxy server was specified but no login "
252 "script, Acquire::ftp::ProxyLogin is empty."));
30b30ec1
AL
253 Opts = Opts->Child;
254
255 // Iterate over the entire login script
256 for (; Opts != 0; Opts = Opts->Next)
257 {
258 if (Opts->Value.empty() == true)
259 continue;
b8eba208 260
30b30ec1 261 // Substitute the variables into the command
30b30ec1
AL
262 string Tmp = Opts->Value;
263 Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
264 Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
265 Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
266 Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
b8eba208
DK
267 if (ServerName.Port != 0)
268 {
269 std::string SitePort;
270 strprintf(SitePort, "%u", ServerName.Port);
271 Tmp = SubstVar(Tmp,"$(SITE_PORT)", SitePort);
272 }
273 else
274 Tmp = SubstVar(Tmp,"$(SITE_PORT)", "21");
30b30ec1
AL
275 Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
276
277 // Send the command
278 if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
279 return false;
280 if (Tag >= 400)
dc738e7a 281 return _error->Error(_("Login script command '%s' failed, server said: %s"),Tmp.c_str(),Msg.c_str());
30b30ec1
AL
282 }
283
284 // Enter passive mode
285 TryPassive = false;
10861bb5
AL
286 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
287 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1
AL
288 else
289 {
10861bb5
AL
290 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
291 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
30b30ec1 292 else
10861bb5 293 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
294 }
295 }
296
b2e465d6
AL
297 // Force the use of extended commands
298 if (_config->Exists("Acquire::FTP::ForceExtended::" + ServerName.Host) == true)
299 ForceExtended = _config->FindB("Acquire::FTP::ForceExtended::" + ServerName.Host,true);
300 else
301 ForceExtended = _config->FindB("Acquire::FTP::ForceExtended",false);
302
30b30ec1
AL
303 // Binary mode
304 if (WriteMsg(Tag,Msg,"TYPE I") == false)
305 return false;
306 if (Tag >= 400)
dc738e7a 307 return _error->Error(_("TYPE failed, server said: %s"),Msg.c_str());
30b30ec1
AL
308
309 return true;
310}
311 /*}}}*/
312// FTPConn::ReadLine - Read a line from the server /*{{{*/
313// ---------------------------------------------------------------------
314/* This performs a very simple buffered read. */
315bool FTPConn::ReadLine(string &Text)
316{
2e90f6e0
AL
317 if (ServerFd == -1)
318 return false;
319
30b30ec1
AL
320 // Suck in a line
321 while (Len < sizeof(Buffer))
322 {
323 // Scan the buffer for a new line
324 for (unsigned int I = 0; I != Len; I++)
325 {
326 // Escape some special chars
327 if (Buffer[I] == 0)
328 Buffer[I] = '?';
329
330 // End of line?
331 if (Buffer[I] != '\n')
332 continue;
333
334 I++;
335 Text = string(Buffer,I);
336 memmove(Buffer,Buffer+I,Len - I);
337 Len -= I;
338 return true;
339 }
340
341 // Wait for some data..
342 if (WaitFd(ServerFd,false,TimeOut) == false)
ce0ae89a
AL
343 {
344 Close();
dc738e7a 345 return _error->Error(_("Connection timeout"));
ce0ae89a 346 }
30b30ec1
AL
347
348 // Suck it back
2e90f6e0 349 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
b2e465d6 350 if (Res == 0)
dc738e7a 351 _error->Error(_("Server closed the connection"));
30b30ec1 352 if (Res <= 0)
ce0ae89a 353 {
dc738e7a 354 _error->Errno("read",_("Read error"));
ce0ae89a 355 Close();
f93d1355 356 return false;
ce0ae89a 357 }
30b30ec1
AL
358 Len += Res;
359 }
360
dc738e7a 361 return _error->Error(_("A response overflowed the buffer."));
30b30ec1
AL
362}
363 /*}}}*/
364// FTPConn::ReadResp - Read a full response from the server /*{{{*/
365// ---------------------------------------------------------------------
366/* This reads a reply code from the server, it handles both p */
367bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
368{
369 // Grab the first line of the response
370 string Msg;
371 if (ReadLine(Msg) == false)
372 return false;
373
374 // Get the ID code
375 char *End;
376 Ret = strtol(Msg.c_str(),&End,10);
377 if (End - Msg.c_str() != 3)
dc738e7a 378 return _error->Error(_("Protocol corruption"));
30b30ec1
AL
379
380 // All done ?
381 Text = Msg.c_str()+4;
382 if (*End == ' ')
383 {
384 if (Debug == true)
ce0ae89a 385 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
386 return true;
387 }
388
389 if (*End != '-')
dc738e7a 390 return _error->Error(_("Protocol corruption"));
30b30ec1
AL
391
392 /* Okay, here we do the continued message trick. This is foolish, but
393 proftpd follows the protocol as specified and wu-ftpd doesn't, so
394 we filter. I wonder how many clients break if you use proftpd and
395 put a '- in the 3rd spot in the message? */
396 char Leader[4];
397 strncpy(Leader,Msg.c_str(),3);
398 Leader[3] = 0;
399 while (ReadLine(Msg) == true)
400 {
401 // Short, it must be using RFC continuation..
402 if (Msg.length() < 4)
403 {
404 Text += Msg;
405 continue;
406 }
407
408 // Oops, finished
409 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
410 {
411 Text += Msg.c_str()+4;
412 break;
413 }
414
415 // This message has the wu-ftpd style reply code prefixed
416 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
417 {
418 Text += Msg.c_str()+4;
419 continue;
420 }
421
422 // Must be RFC style prefixing
423 Text += Msg;
424 }
425
426 if (Debug == true && _error->PendingError() == false)
ce0ae89a 427 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
428
429 return !_error->PendingError();
430}
431 /*}}}*/
432// FTPConn::WriteMsg - Send a message to the server /*{{{*/
433// ---------------------------------------------------------------------
434/* Simple printf like function.. */
435bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
436{
437 va_list args;
438 va_start(args,Fmt);
439
440 // sprintf the description
441 char S[400];
442 vsnprintf(S,sizeof(S) - 4,Fmt,args);
443 strcat(S,"\r\n");
11d0fb91 444 va_end(args);
30b30ec1
AL
445
446 if (Debug == true)
ce0ae89a 447 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
30b30ec1
AL
448
449 // Send it off
450 unsigned long Len = strlen(S);
451 unsigned long Start = 0;
452 while (Len != 0)
453 {
454 if (WaitFd(ServerFd,true,TimeOut) == false)
ce0ae89a
AL
455 {
456 Close();
dc738e7a 457 return _error->Error(_("Connection timeout"));
ce0ae89a 458 }
30b30ec1
AL
459
460 int Res = write(ServerFd,S + Start,Len);
461 if (Res <= 0)
ce0ae89a 462 {
db0db9fe 463 _error->Errno("write",_("Write error"));
ce0ae89a 464 Close();
f93d1355 465 return false;
ce0ae89a
AL
466 }
467
30b30ec1
AL
468 Len -= Res;
469 Start += Res;
470 }
471
472 return ReadResp(Ret,Text);
473}
474 /*}}}*/
475// FTPConn::GoPasv - Enter Passive mode /*{{{*/
476// ---------------------------------------------------------------------
477/* Try to enter passive mode, the return code does not indicate if passive
478 mode could or could not be established, only if there was a fatal error.
b2e465d6 479 We have to enter passive mode every time we make a data connection :| */
30b30ec1
AL
480bool FTPConn::GoPasv()
481{
b2e465d6
AL
482 /* The PASV command only works on IPv4 sockets, even though it could
483 in theory suppory IPv6 via an all zeros reply */
484 if (((struct sockaddr *)&PeerAddr)->sa_family != AF_INET ||
485 ForceExtended == true)
486 return ExtGoPasv();
487
488 if (PasvAddr != 0)
489 freeaddrinfo(PasvAddr);
490 PasvAddr = 0;
491
30b30ec1
AL
492 // Try to enable pasv mode
493 unsigned int Tag;
494 string Msg;
495 if (WriteMsg(Tag,Msg,"PASV") == false)
496 return false;
497
498 // Unsupported function
499 string::size_type Pos = Msg.find('(');
500 if (Tag >= 400 || Pos == string::npos)
30b30ec1 501 return true;
30b30ec1
AL
502
503 // Scan it
504 unsigned a0,a1,a2,a3,p0,p1;
505 if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
b2e465d6
AL
506 return true;
507
508 /* Some evil servers return 0 to mean their addr. We can actually speak
509 to these servers natively using IPv6 */
510 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
30b30ec1 511 {
b2e465d6
AL
512 // Get the IP in text form
513 char Name[NI_MAXHOST];
514 char Service[NI_MAXSERV];
515 getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
516 Name,sizeof(Name),Service,sizeof(Service),
517 NI_NUMERICHOST|NI_NUMERICSERV);
518
519 struct addrinfo Hints;
520 memset(&Hints,0,sizeof(Hints));
521 Hints.ai_socktype = SOCK_STREAM;
522 Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
523 Hints.ai_flags |= AI_NUMERICHOST;
524
525 // Get a new passive address.
526 char Port[100];
527 snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
528 if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
529 return true;
30b30ec1
AL
530 return true;
531 }
532
b2e465d6
AL
533 struct addrinfo Hints;
534 memset(&Hints,0,sizeof(Hints));
535 Hints.ai_socktype = SOCK_STREAM;
536 Hints.ai_family = AF_INET;
537 Hints.ai_flags |= AI_NUMERICHOST;
30b30ec1 538
b2e465d6
AL
539 // Get a new passive address.
540 char Port[100];
541 snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
542 char Name[100];
543 snprintf(Name,sizeof(Name),"%u.%u.%u.%u",a0,a1,a2,a3);
544 if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
545 return true;
546 return true;
547}
548 /*}}}*/
549// FTPConn::ExtGoPasv - Enter Extended Passive mode /*{{{*/
550// ---------------------------------------------------------------------
551/* Try to enter extended passive mode. See GoPasv above and RFC 2428 */
552bool FTPConn::ExtGoPasv()
553{
554 if (PasvAddr != 0)
555 freeaddrinfo(PasvAddr);
556 PasvAddr = 0;
557
558 // Try to enable pasv mode
559 unsigned int Tag;
560 string Msg;
561 if (WriteMsg(Tag,Msg,"EPSV") == false)
562 return false;
563
564 // Unsupported function
565 string::size_type Pos = Msg.find('(');
566 if (Tag >= 400 || Pos == string::npos)
567 return true;
568
569 // Scan it
570 string::const_iterator List[4];
571 unsigned Count = 0;
572 Pos++;
f7f0d6c7 573 for (string::const_iterator I = Msg.begin() + Pos; I < Msg.end(); ++I)
b2e465d6
AL
574 {
575 if (*I != Msg[Pos])
576 continue;
577 if (Count >= 4)
578 return true;
579 List[Count++] = I;
580 }
581 if (Count != 4)
582 return true;
583
584 // Break it up ..
585 unsigned long Proto = 0;
586 unsigned long Port = 0;
587 string IP;
588 IP = string(List[1]+1,List[2]);
589 Port = atoi(string(List[2]+1,List[3]).c_str());
590 if (IP.empty() == false)
591 Proto = atoi(string(List[0]+1,List[1]).c_str());
592
593 if (Port == 0)
594 return false;
595
596 // String version of the port
597 char PStr[100];
598 snprintf(PStr,sizeof(PStr),"%lu",Port);
599
600 // Get the IP in text form
601 struct addrinfo Hints;
602 memset(&Hints,0,sizeof(Hints));
603 Hints.ai_socktype = SOCK_STREAM;
604 Hints.ai_flags |= AI_NUMERICHOST;
605
606 /* The RFC defined case, connect to the old IP/protocol using the
607 new port. */
608 if (IP.empty() == true)
30b30ec1 609 {
b2e465d6
AL
610 // Get the IP in text form
611 char Name[NI_MAXHOST];
612 char Service[NI_MAXSERV];
613 getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
614 Name,sizeof(Name),Service,sizeof(Service),
615 NI_NUMERICHOST|NI_NUMERICSERV);
616 IP = Name;
617 Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
30b30ec1
AL
618 }
619 else
620 {
b2e465d6
AL
621 // Get the family..
622 Hints.ai_family = 0;
623 for (unsigned J = 0; AFMap[J].Family != 0; J++)
624 if (AFMap[J].IETFFamily == Proto)
625 Hints.ai_family = AFMap[J].Family;
626 if (Hints.ai_family == 0)
627 return true;
30b30ec1
AL
628 }
629
b2e465d6 630 // Get a new passive address.
cddbc86d 631 if (getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr) != 0)
b2e465d6 632 return true;
30b30ec1
AL
633
634 return true;
635}
636 /*}}}*/
637// FTPConn::Size - Return the size of a file /*{{{*/
638// ---------------------------------------------------------------------
639/* Grab the file size from the server, 0 means no size or empty file */
650faab0 640bool FTPConn::Size(const char *Path,unsigned long long &Size)
30b30ec1
AL
641{
642 // Query the size
643 unsigned int Tag;
644 string Msg;
ce0ae89a 645 Size = 0;
30b30ec1
AL
646 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
647 return false;
648
649 char *End;
650faab0 650 Size = strtoull(Msg.c_str(),&End,10);
30b30ec1 651 if (Tag >= 400 || End == Msg.c_str())
ce0ae89a
AL
652 Size = 0;
653 return true;
30b30ec1
AL
654}
655 /*}}}*/
656// FTPConn::ModTime - Return the modification time of the file /*{{{*/
657// ---------------------------------------------------------------------
658/* Like Size no error is returned if the command is not supported. If the
659 command fails then time is set to the current time of day to fool
660 date checks. */
661bool FTPConn::ModTime(const char *Path, time_t &Time)
662{
663 Time = time(&Time);
664
665 // Query the mod time
666 unsigned int Tag;
667 string Msg;
668 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
669 return false;
670 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
671 return true;
672
673 // Parse it
96cc64a5 674 return FTPMDTMStrToTime(Msg.c_str(), Time);
30b30ec1
AL
675}
676 /*}}}*/
677// FTPConn::CreateDataFd - Get a data connection /*{{{*/
678// ---------------------------------------------------------------------
679/* Create the data connection. Call FinalizeDataFd after this though.. */
680bool FTPConn::CreateDataFd()
681{
682 close(DataFd);
683 DataFd = -1;
684
685 // Attempt to enter passive mode.
686 if (TryPassive == true)
687 {
688 if (GoPasv() == false)
689 return false;
690
691 // Oops, didn't work out, don't bother trying again.
b2e465d6 692 if (PasvAddr == 0)
30b30ec1
AL
693 TryPassive = false;
694 }
695
696 // Passive mode?
b2e465d6 697 if (PasvAddr != 0)
30b30ec1
AL
698 {
699 // Get a socket
b2e465d6
AL
700 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
701 PasvAddr->ai_protocol)) < 0)
dc738e7a 702 return _error->Errno("socket",_("Could not create a socket"));
30b30ec1
AL
703
704 // Connect to the server
705 SetNonBlock(DataFd,true);
b2e465d6 706 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
30b30ec1 707 errno != EINPROGRESS)
dc738e7a 708 return _error->Errno("socket",_("Could not create a socket"));
30b30ec1
AL
709
710 /* This implements a timeout for connect by opening the connection
6d13bbca 711 nonblocking */
9b148dad 712 if (WaitFd(DataFd,true,TimeOut) == false)
dc738e7a 713 return _error->Error(_("Could not connect data socket, connection timed out"));
30b30ec1
AL
714 unsigned int Err;
715 unsigned int Len = sizeof(Err);
9b148dad 716 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
dc738e7a 717 return _error->Errno("getsockopt",_("Failed"));
30b30ec1 718 if (Err != 0)
dc738e7a 719 return _error->Error(_("Could not connect passive socket."));
b2e465d6 720
30b30ec1
AL
721 return true;
722 }
723
724 // Port mode :<
2de438e7
AL
725 close(DataListenFd);
726 DataListenFd = -1;
727
b2e465d6 728 // Get the information for a listening socket.
ce3c2407 729 struct addrinfo *BindAddr = NULL;
b2e465d6
AL
730 struct addrinfo Hints;
731 memset(&Hints,0,sizeof(Hints));
732 Hints.ai_socktype = SOCK_STREAM;
733 Hints.ai_flags |= AI_PASSIVE;
734 Hints.ai_family = ((struct sockaddr *)&ServerAddr)->sa_family;
cddbc86d 735 if (getaddrinfo(0,"0",&Hints,&BindAddr) != 0 || BindAddr == NULL)
dc738e7a 736 return _error->Error(_("getaddrinfo was unable to get a listening socket"));
b2e465d6
AL
737
738 // Construct the socket
739 if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
740 BindAddr->ai_protocol)) < 0)
741 {
742 freeaddrinfo(BindAddr);
dc738e7a 743 return _error->Errno("socket",_("Could not create a socket"));
b2e465d6 744 }
30b30ec1 745
2de438e7 746 // Bind and listen
f8043f21 747 if (::bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
b2e465d6
AL
748 {
749 freeaddrinfo(BindAddr);
dc738e7a 750 return _error->Errno("bind",_("Could not bind a socket"));
b2e465d6
AL
751 }
752 freeaddrinfo(BindAddr);
2de438e7 753 if (listen(DataListenFd,1) < 0)
dc738e7a 754 return _error->Errno("listen",_("Could not listen on the socket"));
2de438e7
AL
755 SetNonBlock(DataListenFd,true);
756
757 // Determine the name to send to the remote
b2e465d6
AL
758 struct sockaddr_storage Addr;
759 socklen_t AddrLen = sizeof(Addr);
760 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
dc738e7a
AL
761 return _error->Errno("getsockname",_("Could not determine the socket's name"));
762
30b30ec1 763
b2e465d6
AL
764 // Reverse the address. We need the server address and the data port.
765 char Name[NI_MAXHOST];
766 char Service[NI_MAXSERV];
767 char Service2[NI_MAXSERV];
768 getnameinfo((struct sockaddr *)&Addr,AddrLen,
769 Name,sizeof(Name),Service,sizeof(Service),
770 NI_NUMERICHOST|NI_NUMERICSERV);
771 getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
772 Name,sizeof(Name),Service2,sizeof(Service2),
773 NI_NUMERICHOST|NI_NUMERICSERV);
774
775 // Send off an IPv4 address in the old port format
776 if (((struct sockaddr *)&Addr)->sa_family == AF_INET &&
777 ForceExtended == false)
778 {
779 // Convert the dots in the quad into commas
780 for (char *I = Name; *I != 0; I++)
781 if (*I == '.')
782 *I = ',';
783 unsigned long Port = atoi(Service);
784
785 // Send the port command
786 unsigned int Tag;
787 string Msg;
788 if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
789 Name,
790 (int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
791 return false;
792 if (Tag >= 400)
dc738e7a 793 return _error->Error(_("Unable to send PORT command"));
b2e465d6
AL
794 return true;
795 }
796
797 // Construct an EPRT command
798 unsigned Proto = 0;
799 for (unsigned J = 0; AFMap[J].Family != 0; J++)
800 if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
801 Proto = AFMap[J].IETFFamily;
802 if (Proto == 0)
dc738e7a 803 return _error->Error(_("Unknown address family %u (AF_*)"),
b2e465d6
AL
804 ((struct sockaddr *)&Addr)->sa_family);
805
806 // Send the EPRT command
30b30ec1
AL
807 unsigned int Tag;
808 string Msg;
b2e465d6 809 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
30b30ec1
AL
810 return false;
811 if (Tag >= 400)
dc738e7a 812 return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
30b30ec1
AL
813 return true;
814}
815 /*}}}*/
816// FTPConn::Finalize - Complete the Data connection /*{{{*/
817// ---------------------------------------------------------------------
818/* If the connection is in port mode this waits for the other end to hook
819 up to us. */
820bool FTPConn::Finalize()
821{
822 // Passive mode? Do nothing
b2e465d6 823 if (PasvAddr != 0)
30b30ec1
AL
824 return true;
825
826 // Close any old socket..
827 close(DataFd);
828 DataFd = -1;
829
830 // Wait for someone to connect..
831 if (WaitFd(DataListenFd,false,TimeOut) == false)
dc738e7a 832 return _error->Error(_("Data socket connect timed out"));
30b30ec1
AL
833
834 // Accept the connection
835 struct sockaddr_in Addr;
836 socklen_t Len = sizeof(Addr);
837 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
838 if (DataFd < 0)
dc738e7a 839 return _error->Errno("accept",_("Unable to accept connection"));
30b30ec1 840
2de438e7
AL
841 close(DataListenFd);
842 DataListenFd = -1;
843
30b30ec1
AL
844 return true;
845}
846 /*}}}*/
847// FTPConn::Get - Get a file /*{{{*/
848// ---------------------------------------------------------------------
849/* This opens a data connection, sends REST and RETR and then
850 transfers the file over. */
650faab0 851bool FTPConn::Get(const char *Path,FileFd &To,unsigned long long Resume,
ee279506
MV
852 Hashes &Hash,bool &Missing, unsigned long long MaximumSize,
853 pkgAcqMethod *Owner)
30b30ec1 854{
ce0ae89a 855 Missing = false;
30b30ec1
AL
856 if (CreateDataFd() == false)
857 return false;
858
859 unsigned int Tag;
ce0ae89a 860 string Msg;
30b30ec1
AL
861 if (Resume != 0)
862 {
863 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
864 return false;
865 if (Tag >= 400)
866 Resume = 0;
867 }
868
869 if (To.Truncate(Resume) == false)
870 return false;
10861bb5
AL
871
872 if (To.Seek(0) == false)
873 return false;
874
875 if (Resume != 0)
876 {
109eb151 877 if (Hash.AddFD(To,Resume) == false)
10861bb5 878 {
dc738e7a 879 _error->Errno("read",_("Problem hashing file"));
10861bb5
AL
880 return false;
881 }
882 }
30b30ec1
AL
883
884 // Send the get command
885 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
886 return false;
887
888 if (Tag >= 400)
ce0ae89a
AL
889 {
890 if (Tag == 550)
891 Missing = true;
dc738e7a 892 return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
ce0ae89a 893 }
30b30ec1
AL
894
895 // Finish off the data connection
896 if (Finalize() == false)
897 return false;
898
899 // Copy loop
900 unsigned char Buffer[4096];
901 while (1)
902 {
903 // Wait for some data..
904 if (WaitFd(DataFd,false,TimeOut) == false)
25dbb396
AL
905 {
906 Close();
dc738e7a 907 return _error->Error(_("Data socket timed out"));
25dbb396
AL
908 }
909
30b30ec1
AL
910 // Read the data..
911 int Res = read(DataFd,Buffer,sizeof(Buffer));
912 if (Res == 0)
913 break;
914 if (Res < 0)
915 {
916 if (errno == EAGAIN)
917 continue;
918 break;
919 }
10861bb5 920
63b1700f 921 Hash.Add(Buffer,Res);
30b30ec1 922 if (To.Write(Buffer,Res) == false)
25dbb396
AL
923 {
924 Close();
30b30ec1 925 return false;
5b33fab8
MV
926 }
927
c48eea97 928 if (MaximumSize > 0 && To.Tell() > MaximumSize)
ee279506
MV
929 {
930 Owner->SetFailReason("MaximumSizeExceeded");
5b33fab8 931 return _error->Error("Writing more data than expected (%llu > %llu)",
c48eea97 932 To.Tell(), MaximumSize);
ee279506 933 }
30b30ec1
AL
934 }
935
936 // All done
937 close(DataFd);
938 DataFd = -1;
939
940 // Read the closing message from the server
941 if (ReadResp(Tag,Msg) == false)
942 return false;
943 if (Tag >= 400)
dc738e7a 944 return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
30b30ec1
AL
945 return true;
946}
947 /*}}}*/
948
ce0ae89a
AL
949// FtpMethod::FtpMethod - Constructor /*{{{*/
950// ---------------------------------------------------------------------
951/* */
23e64f6d 952FtpMethod::FtpMethod() : aptMethod("ftp","1.0",SendConfig)
30b30ec1 953{
ce0ae89a
AL
954 signal(SIGTERM,SigTerm);
955 signal(SIGINT,SigTerm);
30b30ec1 956
ce0ae89a
AL
957 Server = 0;
958 FailFd = -1;
959}
960 /*}}}*/
961// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
962// ---------------------------------------------------------------------
1e3f4083 963/* This closes and timestamps the open file. This is necessary to get
ce0ae89a
AL
964 resume behavoir on user abort */
965void FtpMethod::SigTerm(int)
966{
967 if (FailFd == -1)
ffe9323a 968 _exit(100);
9ce3cfc9 969
ce0ae89a 970 // Timestamp
246bbb61 971 struct timeval times[2];
9ce3cfc9
DK
972 times[0].tv_sec = FailTime;
973 times[1].tv_sec = FailTime;
246bbb61
DK
974 times[0].tv_usec = times[1].tv_usec = 0;
975 utimes(FailFile.c_str(), times);
9ce3cfc9
DK
976
977 close(FailFd);
978
ffe9323a 979 _exit(100);
ce0ae89a
AL
980}
981 /*}}}*/
982// FtpMethod::Configuration - Handle a configuration message /*{{{*/
983// ---------------------------------------------------------------------
984/* We stash the desired pipeline depth */
985bool FtpMethod::Configuration(string Message)
986{
23e64f6d 987 if (aptMethod::Configuration(Message) == false)
ce0ae89a 988 return false;
9983999d 989
23e64f6d 990 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
9983999d 991
ce0ae89a
AL
992 return true;
993}
994 /*}}}*/
995// FtpMethod::Fetch - Fetch a file /*{{{*/
996// ---------------------------------------------------------------------
10861bb5 997/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
998bool FtpMethod::Fetch(FetchItem *Itm)
999{
1000 URI Get = Itm->Uri;
1001 const char *File = Get.Path.c_str();
1002 FetchResult Res;
1003 Res.Filename = Itm->DestFile;
1004 Res.IMSHit = false;
1de1f703
MV
1005
1006 maybe_add_auth (Get, _config->FindFile("Dir::Etc::netrc"));
1007
ce0ae89a
AL
1008 // Connect to the server
1009 if (Server == 0 || Server->Comp(Get) == false)
30b30ec1 1010 {
ce0ae89a
AL
1011 delete Server;
1012 Server = new FTPConn(Get);
1013 }
1014
1015 // Could not connect is a transient error..
1016 if (Server->Open(this) == false)
1017 {
b3e53cec 1018 Server->Close();
ce0ae89a
AL
1019 Fail(true);
1020 return true;
1021 }
30b30ec1 1022
ce0ae89a 1023 // Get the files information
dc738e7a 1024 Status(_("Query"));
650faab0 1025 unsigned long long Size;
ce0ae89a
AL
1026 if (Server->Size(File,Size) == false ||
1027 Server->ModTime(File,FailTime) == false)
1028 {
1029 Fail(true);
1030 return true;
1031 }
1032 Res.Size = Size;
1033
1034 // See if it is an IMS hit
1035 if (Itm->LastModified == FailTime)
1036 {
1037 Res.Size = 0;
1038 Res.IMSHit = true;
1039 URIDone(Res);
1040 return true;
1041 }
1042
1043 // See if the file exists
1044 struct stat Buf;
1045 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
1046 {
650faab0 1047 if (Size == (unsigned long long)Buf.st_size && FailTime == Buf.st_mtime)
30b30ec1 1048 {
ce0ae89a
AL
1049 Res.Size = Buf.st_size;
1050 Res.LastModified = Buf.st_mtime;
7ef72446 1051 Res.ResumePoint = Buf.st_size;
ce0ae89a
AL
1052 URIDone(Res);
1053 return true;
30b30ec1
AL
1054 }
1055
ce0ae89a 1056 // Resume?
650faab0 1057 if (FailTime == Buf.st_mtime && Size > (unsigned long long)Buf.st_size)
ce0ae89a
AL
1058 Res.ResumePoint = Buf.st_size;
1059 }
1060
1061 // Open the file
9224ce3d 1062 Hashes Hash(Itm->ExpectedHashes);
ce0ae89a
AL
1063 {
1064 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
1065 if (_error->PendingError() == true)
1066 return false;
1067
1068 URIStart(Res);
1069
1070 FailFile = Itm->DestFile;
3a8776a3 1071 FailFile.c_str(); // Make sure we don't do a malloc in the signal handler
ce0ae89a
AL
1072 FailFd = Fd.Fd();
1073
1074 bool Missing;
ee279506 1075 if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing,Itm->MaximumSize,this) == false)
30b30ec1 1076 {
0dfc0829 1077 Fd.Close();
9ce3cfc9 1078
0dfc0829 1079 // Timestamp
246bbb61 1080 struct timeval times[2];
9ce3cfc9
DK
1081 times[0].tv_sec = FailTime;
1082 times[1].tv_sec = FailTime;
246bbb61
DK
1083 times[0].tv_usec = times[1].tv_usec = 0;
1084 utimes(FailFile.c_str(), times);
9ce3cfc9 1085
fc5f5417
MV
1086 // If the file is missing we hard fail and delete the destfile
1087 // otherwise transient fail
1088 if (Missing == true) {
ce1f3a2c 1089 RemoveFile("ftp", FailFile);
ce0ae89a 1090 return false;
fc5f5417 1091 }
ce0ae89a
AL
1092 Fail(true);
1093 return true;
30b30ec1 1094 }
ce0ae89a
AL
1095
1096 Res.Size = Fd.Size();
9ce3cfc9
DK
1097
1098 // Timestamp
246bbb61 1099 struct timeval times[2];
9ce3cfc9
DK
1100 times[0].tv_sec = FailTime;
1101 times[1].tv_sec = FailTime;
246bbb61
DK
1102 times[0].tv_usec = times[1].tv_usec = 0;
1103 utimes(Fd.Name().c_str(), times);
9ce3cfc9 1104 FailFd = -1;
30b30ec1 1105 }
9ce3cfc9 1106
ce0ae89a 1107 Res.LastModified = FailTime;
a7c835af 1108 Res.TakeHashes(Hash);
ce0ae89a
AL
1109
1110 URIDone(Res);
9ce3cfc9 1111
ce0ae89a
AL
1112 return true;
1113}
1114 /*}}}*/
1115
65512241 1116int main(int, const char *argv[])
8b79c94a 1117{
d4489322
AL
1118 /* See if we should be come the http client - we do this for http
1119 proxy urls */
1120 if (getenv("ftp_proxy") != 0)
1121 {
1122 URI Proxy = string(getenv("ftp_proxy"));
f8081133 1123
f8081133 1124 // Run the HTTP method
d4489322
AL
1125 if (Proxy.Access == "http")
1126 {
1127 // Copy over the environment setting
1128 char S[300];
1129 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
1130 putenv(S);
b8564336 1131 putenv((char *)"no_proxy=");
d4489322
AL
1132
1133 // Run the http method
91bb3e2e 1134 string Path = flNotFile(argv[0]) + "http";
42ab8223 1135 execl(Path.c_str(),Path.c_str(),(char *)NULL);
dc738e7a 1136 cerr << _("Unable to invoke ") << Path << endl;
d4489322
AL
1137 exit(100);
1138 }
1139 }
8b79c94a 1140 return FtpMethod().Run();
30b30ec1 1141}