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