]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
Reordered some things to make dante happier. Closes: #92757
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.27 2001/04/10 04:51:50 jgg Exp $
4 /* ######################################################################
5
6 FTP 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.
12
13 RFC 2428 describes the IPv6 FTP behavior
14
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/hashes.h>
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
38 #include "rfc2553emu.h"
39 #include "connect.h"
40 #include "ftp.h"
41 /*}}}*/
42
43 /* This table is for the EPRT and EPSV commands, it maps the OS address
44 family to the IETF address families */
45 struct AFMap
46 {
47 unsigned long Family;
48 unsigned long IETFFamily;
49 };
50
51 #ifndef AF_INET6
52 struct AFMap AFMap[] = {{AF_INET,1},{}};
53 #else
54 struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{}};
55 #endif
56
57 unsigned long TimeOut = 120;
58 URI Proxy;
59 string FtpMethod::FailFile;
60 int FtpMethod::FailFd = -1;
61 time_t FtpMethod::FailTime = 0;
62
63 // FTPConn::FTPConn - Constructor /*{{{*/
64 // ---------------------------------------------------------------------
65 /* */
66 FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
67 DataListenFd(-1), ServerName(Srv)
68 {
69 Debug = _config->FindB("Debug::Acquire::Ftp",false);
70 PasvAddr = 0;
71 }
72 /*}}}*/
73 // FTPConn::~FTPConn - Destructor /*{{{*/
74 // ---------------------------------------------------------------------
75 /* */
76 FTPConn::~FTPConn()
77 {
78 Close();
79 }
80 /*}}}*/
81 // FTPConn::Close - Close down the connection /*{{{*/
82 // ---------------------------------------------------------------------
83 /* Just tear down the socket and data socket */
84 void FTPConn::Close()
85 {
86 close(ServerFd);
87 ServerFd = -1;
88 close(DataFd);
89 DataFd = -1;
90 close(DataListenFd);
91 DataListenFd = -1;
92
93 if (PasvAddr != 0)
94 freeaddrinfo(PasvAddr);
95 PasvAddr = 0;
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. */
102 bool FTPConn::Open(pkgAcqMethod *Owner)
103 {
104 // Use the already open connection if possible.
105 if (ServerFd != -1)
106 return true;
107
108 Close();
109
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
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
135 // Determine what host and port to use based on the proxy settings
136 int Port = 0;
137 string Host;
138 if (Proxy.empty() == true)
139 {
140 if (ServerName.Port != 0)
141 Port = ServerName.Port;
142 Host = ServerName.Host;
143 }
144 else
145 {
146 if (Proxy.Port != 0)
147 Port = Proxy.Port;
148 Host = Proxy.Host;
149 }
150
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();
154 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
155 return false;
156
157 // Login must be before getpeername otherwise dante won't work.
158 Owner->Status("Logging in");
159 bool Res = Login();
160
161 // Get the remote server's address
162 PeerAddrLen = sizeof(PeerAddr);
163 if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
164 return _error->Errno("getpeername","Unable to determine the peer name");
165
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
171 return Res;
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. */
178 bool FTPConn::Login()
179 {
180 unsigned int Tag;
181 string Msg;
182
183 // Setup the variables needed for authentication
184 string User = "anonymous";
185 string Pass = "apt_get_ftp_2.1@debian.linux.user";
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
215 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
216 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
217 else
218 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
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];
243 if (ServerName.Port != 0)
244 sprintf(SitePort,"%u",ServerName.Port);
245 else
246 strcpy(SitePort,"21");
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;
264 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
265 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
266 else
267 {
268 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
269 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
270 else
271 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
272 }
273 }
274
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
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. */
293 bool FTPConn::ReadLine(string &Text)
294 {
295 if (ServerFd == -1)
296 return false;
297
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)
321 {
322 Close();
323 return _error->Error("Connection timeout");
324 }
325
326 // Suck it back
327 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
328 if (Res == 0)
329 _error->Error("Server closed the connection");
330 if (Res <= 0)
331 {
332 _error->Errno("read","Read error");
333 Close();
334 return false;
335 }
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 */
345 bool 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)
363 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
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)
405 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
406
407 return !_error->PendingError();
408 }
409 /*}}}*/
410 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
411 // ---------------------------------------------------------------------
412 /* Simple printf like function.. */
413 bool 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)
424 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
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)
432 {
433 Close();
434 return _error->Error("Connection timeout");
435 }
436
437 int Res = write(ServerFd,S + Start,Len);
438 if (Res <= 0)
439 {
440 _error->Errno("write","Write Error");
441 Close();
442 return false;
443 }
444
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.
456 We have to enter passive mode every time we make a data connection :| */
457 bool FTPConn::GoPasv()
458 {
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
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)
478 return true;
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)
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)
488 {
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;
507 return true;
508 }
509
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;
515
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 */
529 bool 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)
586 {
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;
595 }
596 else
597 {
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;
605 }
606
607 // Get a new passive address.
608 int Res;
609 if ((Res = getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr)) != 0)
610 return true;
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 */
618 bool FTPConn::Size(const char *Path,unsigned long &Size)
619 {
620 // Query the size
621 unsigned int Tag;
622 string Msg;
623 Size = 0;
624 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
625 return false;
626
627 char *End;
628 Size = strtol(Msg.c_str(),&End,10);
629 if (Tag >= 400 || End == Msg.c_str())
630 Size = 0;
631 return true;
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. */
639 bool 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
652 StrToTime(Msg,Time);
653 return true;
654 }
655 /*}}}*/
656 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
657 // ---------------------------------------------------------------------
658 /* Create the data connection. Call FinalizeDataFd after this though.. */
659 bool 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.
671 if (PasvAddr == 0)
672 TryPassive = false;
673 }
674
675 // Passive mode?
676 if (PasvAddr != 0)
677 {
678 // Get a socket
679 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
680 PasvAddr->ai_protocol)) < 0)
681 return _error->Errno("socket","Could not create a socket");
682
683 // Connect to the server
684 SetNonBlock(DataFd,true);
685 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
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
690 nonblocking */
691 if (WaitFd(DataFd,true,TimeOut) == false)
692 return _error->Error("Could not connect data socket, connection timed out");
693 unsigned int Err;
694 unsigned int Len = sizeof(Err);
695 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
696 return _error->Errno("getsockopt","Failed");
697 if (Err != 0)
698 return _error->Error("Could not connect passive socket.");
699
700 return true;
701 }
702
703 // Port mode :<
704 close(DataListenFd);
705 DataListenFd = -1;
706
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);
723 return _error->Errno("socket","Could not create a socket");
724 }
725
726 // Bind and listen
727 if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
728 {
729 freeaddrinfo(BindAddr);
730 return _error->Errno("bind","Could not bind a socket");
731 }
732 freeaddrinfo(BindAddr);
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
738 struct sockaddr_storage Addr;
739 socklen_t AddrLen = sizeof(Addr);
740 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
741 return _error->Errno("getsockname","Could not determine the socket's name");
742
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
786 unsigned int Tag;
787 string Msg;
788 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
789 return false;
790 if (Tag >= 400)
791 return _error->Error("EPRT failed, server said: %s",Msg.c_str());
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. */
799 bool FTPConn::Finalize()
800 {
801 // Passive mode? Do nothing
802 if (PasvAddr != 0)
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
820 close(DataListenFd);
821 DataListenFd = -1;
822
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. */
830 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
831 Hashes &Hash,bool &Missing)
832 {
833 Missing = false;
834 if (CreateDataFd() == false)
835 return false;
836
837 unsigned int Tag;
838 string Msg;
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;
849
850 if (To.Seek(0) == false)
851 return false;
852
853 if (Resume != 0)
854 {
855 if (Hash.AddFD(To.Fd(),Resume) == false)
856 {
857 _error->Errno("read","Problem hashing file");
858 return false;
859 }
860 }
861
862 // Send the get command
863 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
864 return false;
865
866 if (Tag >= 400)
867 {
868 if (Tag == 550)
869 Missing = true;
870 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
871 }
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)
883 {
884 Close();
885 return _error->Error("Data socket timed out");
886 }
887
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 }
898
899 Hash.Add(Buffer,Res);
900 if (To.Write(Buffer,Res) == false)
901 {
902 Close();
903 return false;
904 }
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
920 // FtpMethod::FtpMethod - Constructor /*{{{*/
921 // ---------------------------------------------------------------------
922 /* */
923 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
924 {
925 signal(SIGTERM,SigTerm);
926 signal(SIGINT,SigTerm);
927
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 */
936 void FtpMethod::SigTerm(int)
937 {
938 if (FailFd == -1)
939 _exit(100);
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
948 _exit(100);
949 }
950 /*}}}*/
951 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
952 // ---------------------------------------------------------------------
953 /* We stash the desired pipeline depth */
954 bool 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 // ---------------------------------------------------------------------
965 /* Fetch a single file, called by the base class.. */
966 bool 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)
976 {
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 {
984 Server->Close();
985 Fail(true);
986 return true;
987 }
988
989 // Get the files information
990 Status("Query");
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)
1014 {
1015 Res.Size = Buf.st_size;
1016 Res.LastModified = Buf.st_mtime;
1017 Res.ResumePoint = Buf.st_size;
1018 URIDone(Res);
1019 return true;
1020 }
1021
1022 // Resume?
1023 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
1024 Res.ResumePoint = Buf.st_size;
1025 }
1026
1027 // Open the file
1028 Hashes Hash;
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;
1041 if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing) == false)
1042 {
1043 Fd.Close();
1044
1045 // Timestamp
1046 struct utimbuf UBuf;
1047 UBuf.actime = FailTime;
1048 UBuf.modtime = FailTime;
1049 utime(FailFile.c_str(),&UBuf);
1050
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;
1056 }
1057
1058 Res.Size = Fd.Size();
1059 }
1060
1061 Res.LastModified = FailTime;
1062 Res.TakeHashes(Hash);
1063
1064 // Timestamp
1065 struct utimbuf UBuf;
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
1077 int main(int argc,const char *argv[])
1078 {
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"));
1084
1085 // Run the HTTP method
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);
1092 putenv("no_proxy=");
1093
1094 // Run the http method
1095 string Path = flNotFile(argv[0]) + "/http";
1096 execl(Path.c_str(),Path.c_str(),0);
1097 cerr << "Unable to invoke " << Path << endl;
1098 exit(100);
1099 }
1100 }
1101
1102 FtpMethod Mth;
1103
1104 return Mth.Run();
1105 }