]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
a3a7cd08a63a308af8197b33341911ec5aa66cf5
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.25 2001/03/13 06:51:46 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 // Get the remote server's address
158 PeerAddrLen = sizeof(PeerAddr);
159 if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
160 return _error->Errno("getpeername","Unable to determine the peer name");
161
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
167 Owner->Status("Logging in");
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. */
175 bool 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.1@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
212 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
213 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
214 else
215 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
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];
240 if (ServerName.Port != 0)
241 sprintf(SitePort,"%u",ServerName.Port);
242 else
243 strcpy(SitePort,"21");
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;
261 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
262 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
263 else
264 {
265 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
266 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
267 else
268 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
269 }
270 }
271
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
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. */
290 bool FTPConn::ReadLine(string &Text)
291 {
292 if (ServerFd == -1)
293 return false;
294
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)
318 {
319 Close();
320 return _error->Error("Connection timeout");
321 }
322
323 // Suck it back
324 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
325 if (Res == 0)
326 _error->Error("Server closed the connection");
327 if (Res <= 0)
328 {
329 _error->Errno("read","Read error");
330 Close();
331 return false;
332 }
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 */
342 bool 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)
360 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
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)
402 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
403
404 return !_error->PendingError();
405 }
406 /*}}}*/
407 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
408 // ---------------------------------------------------------------------
409 /* Simple printf like function.. */
410 bool 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)
421 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
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)
429 {
430 Close();
431 return _error->Error("Connection timeout");
432 }
433
434 int Res = write(ServerFd,S + Start,Len);
435 if (Res <= 0)
436 {
437 _error->Errno("write","Write Error");
438 Close();
439 return false;
440 }
441
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.
453 We have to enter passive mode every time we make a data connection :| */
454 bool FTPConn::GoPasv()
455 {
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
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)
475 return true;
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)
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)
485 {
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;
504 return true;
505 }
506
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;
512
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 */
526 bool 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)
583 {
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;
592 }
593 else
594 {
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;
602 }
603
604 // Get a new passive address.
605 int Res;
606 if ((Res = getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr)) != 0)
607 return true;
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 */
615 bool FTPConn::Size(const char *Path,unsigned long &Size)
616 {
617 // Query the size
618 unsigned int Tag;
619 string Msg;
620 Size = 0;
621 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
622 return false;
623
624 char *End;
625 Size = strtol(Msg.c_str(),&End,10);
626 if (Tag >= 400 || End == Msg.c_str())
627 Size = 0;
628 return true;
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. */
636 bool 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
649 StrToTime(Msg,Time);
650 return true;
651 }
652 /*}}}*/
653 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
654 // ---------------------------------------------------------------------
655 /* Create the data connection. Call FinalizeDataFd after this though.. */
656 bool 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.
668 if (PasvAddr == 0)
669 TryPassive = false;
670 }
671
672 // Passive mode?
673 if (PasvAddr != 0)
674 {
675 // Get a socket
676 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
677 PasvAddr->ai_protocol)) < 0)
678 return _error->Errno("socket","Could not create a socket");
679
680 // Connect to the server
681 SetNonBlock(DataFd,true);
682 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
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
687 nonblocking */
688 if (WaitFd(DataFd,true,TimeOut) == false)
689 return _error->Error("Could not connect data socket, connection timed out");
690 unsigned int Err;
691 unsigned int Len = sizeof(Err);
692 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
693 return _error->Errno("getsockopt","Failed");
694 if (Err != 0)
695 return _error->Error("Could not connect passive socket.");
696
697 return true;
698 }
699
700 // Port mode :<
701 close(DataListenFd);
702 DataListenFd = -1;
703
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);
720 return _error->Errno("socket","Could not create a socket");
721 }
722
723 // Bind and listen
724 if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
725 {
726 freeaddrinfo(BindAddr);
727 return _error->Errno("bind","Could not bind a socket");
728 }
729 freeaddrinfo(BindAddr);
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
735 struct sockaddr_storage Addr;
736 socklen_t AddrLen = sizeof(Addr);
737 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
738 return _error->Errno("getsockname","Could not determine the socket's name");
739
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
783 unsigned int Tag;
784 string Msg;
785 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
786 return false;
787 if (Tag >= 400)
788 return _error->Error("EPRT failed, server said: %s",Msg.c_str());
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. */
796 bool FTPConn::Finalize()
797 {
798 // Passive mode? Do nothing
799 if (PasvAddr != 0)
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
817 close(DataListenFd);
818 DataListenFd = -1;
819
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. */
827 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
828 Hashes &Hash,bool &Missing)
829 {
830 Missing = false;
831 if (CreateDataFd() == false)
832 return false;
833
834 unsigned int Tag;
835 string Msg;
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;
846
847 if (To.Seek(0) == false)
848 return false;
849
850 if (Resume != 0)
851 {
852 if (Hash.AddFD(To.Fd(),Resume) == false)
853 {
854 _error->Errno("read","Problem hashing file");
855 return false;
856 }
857 }
858
859 // Send the get command
860 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
861 return false;
862
863 if (Tag >= 400)
864 {
865 if (Tag == 550)
866 Missing = true;
867 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
868 }
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)
880 {
881 Close();
882 return _error->Error("Data socket timed out");
883 }
884
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 }
895
896 Hash.Add(Buffer,Res);
897 if (To.Write(Buffer,Res) == false)
898 {
899 Close();
900 return false;
901 }
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
917 // FtpMethod::FtpMethod - Constructor /*{{{*/
918 // ---------------------------------------------------------------------
919 /* */
920 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
921 {
922 signal(SIGTERM,SigTerm);
923 signal(SIGINT,SigTerm);
924
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 */
933 void FtpMethod::SigTerm(int)
934 {
935 if (FailFd == -1)
936 _exit(100);
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
945 _exit(100);
946 }
947 /*}}}*/
948 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
949 // ---------------------------------------------------------------------
950 /* We stash the desired pipeline depth */
951 bool 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 // ---------------------------------------------------------------------
962 /* Fetch a single file, called by the base class.. */
963 bool 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)
973 {
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 {
981 Server->Close();
982 Fail(true);
983 return true;
984 }
985
986 // Get the files information
987 Status("Query");
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)
1011 {
1012 Res.Size = Buf.st_size;
1013 Res.LastModified = Buf.st_mtime;
1014 Res.ResumePoint = Buf.st_size;
1015 URIDone(Res);
1016 return true;
1017 }
1018
1019 // Resume?
1020 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
1021 Res.ResumePoint = Buf.st_size;
1022 }
1023
1024 // Open the file
1025 Hashes Hash;
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,Hash,Missing) == false)
1039 {
1040 Fd.Close();
1041
1042 // Timestamp
1043 struct utimbuf UBuf;
1044 UBuf.actime = FailTime;
1045 UBuf.modtime = FailTime;
1046 utime(FailFile.c_str(),&UBuf);
1047
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;
1053 }
1054
1055 Res.Size = Fd.Size();
1056 }
1057
1058 Res.LastModified = FailTime;
1059 Res.TakeHashes(Hash);
1060
1061 // Timestamp
1062 struct utimbuf UBuf;
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
1074 int main(int argc,const char *argv[])
1075 {
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"));
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
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";
1099 execl(Path.c_str(),Path.c_str(),0);
1100 cerr << "Unable to invoke " << Path << endl;
1101 exit(100);
1102 }
1103 }
1104
1105 FtpMethod Mth;
1106
1107 return Mth.Run();
1108 }