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