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