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