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