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