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