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