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