]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
Better handling of missing services
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.14 1999/07/18 23:06:56 jgg Exp $
4 /* ######################################################################
5
6 HTTP Aquire Method - This is the FTP aquire method for APT.
7
8 This is a very simple implementation that does not try to optimize
9 at all. Commands are sent syncronously with the FTP server (as the
10 rfc recommends, but it is not really necessary..) and no tricks are
11 done to speed things along.
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/md5.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 unsigned long TimeOut = 120;
44 URI Proxy;
45 string FtpMethod::FailFile;
46 int FtpMethod::FailFd = -1;
47 time_t FtpMethod::FailTime = 0;
48
49 // FTPConn::FTPConn - Constructor /*{{{*/
50 // ---------------------------------------------------------------------
51 /* */
52 FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
53 DataListenFd(-1), ServerName(Srv)
54 {
55 Debug = _config->FindB("Debug::Acquire::Ftp",false);
56 memset(&PasvAddr,0,sizeof(PasvAddr));
57 }
58 /*}}}*/
59 // FTPConn::~FTPConn - Destructor /*{{{*/
60 // ---------------------------------------------------------------------
61 /* */
62 FTPConn::~FTPConn()
63 {
64 Close();
65 }
66 /*}}}*/
67 // FTPConn::Close - Close down the connection /*{{{*/
68 // ---------------------------------------------------------------------
69 /* Just tear down the socket and data socket */
70 void FTPConn::Close()
71 {
72 close(ServerFd);
73 ServerFd = -1;
74 close(DataFd);
75 DataFd = -1;
76 close(DataListenFd);
77 DataListenFd = -1;
78 memset(&PasvAddr,0,sizeof(PasvAddr));
79 }
80 /*}}}*/
81 // FTPConn::Open - Open a new connection /*{{{*/
82 // ---------------------------------------------------------------------
83 /* Connect to the server using a non-blocking connection and perform a
84 login. */
85 bool FTPConn::Open(pkgAcqMethod *Owner)
86 {
87 // Use the already open connection if possible.
88 if (ServerFd != -1)
89 return true;
90
91 Close();
92
93 // Determine the proxy setting
94 if (getenv("ftp_proxy") == 0)
95 {
96 string DefProxy = _config->Find("Acquire::ftp::Proxy");
97 string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
98 if (SpecificProxy.empty() == false)
99 {
100 if (SpecificProxy == "DIRECT")
101 Proxy = "";
102 else
103 Proxy = SpecificProxy;
104 }
105 else
106 Proxy = DefProxy;
107 }
108 else
109 Proxy = getenv("ftp_proxy");
110
111 // Determine what host and port to use based on the proxy settings
112 int Port = 0;
113 string Host;
114 if (Proxy.empty() == true)
115 {
116 if (ServerName.Port != 0)
117 Port = ServerName.Port;
118 Host = ServerName.Host;
119 }
120 else
121 {
122 if (Proxy.Port != 0)
123 Port = Proxy.Port;
124 Host = Proxy.Host;
125 }
126
127 // Connect to the remote server
128 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
129 return false;
130 socklen_t Len = sizeof(Peer);
131 if (getpeername(ServerFd,(sockaddr *)&Peer,&Len) != 0)
132 return _error->Errno("getpeername","Unable to determine the peer name");
133
134 Owner->Status("Logging in");
135 return Login();
136 }
137 /*}}}*/
138 // FTPConn::Login - Login to the remote server /*{{{*/
139 // ---------------------------------------------------------------------
140 /* This performs both normal login and proxy login using a simples script
141 stored in the config file. */
142 bool FTPConn::Login()
143 {
144 unsigned int Tag;
145 string Msg;
146
147 // Setup the variables needed for authentication
148 string User = "anonymous";
149 string Pass = "apt_get_ftp_2.0@debian.linux.user";
150
151 // Fill in the user/pass
152 if (ServerName.User.empty() == false)
153 User = ServerName.User;
154 if (ServerName.Password.empty() == false)
155 Pass = ServerName.Password;
156
157 // Perform simple login
158 if (Proxy.empty() == true)
159 {
160 // Read the initial response
161 if (ReadResp(Tag,Msg) == false)
162 return false;
163 if (Tag >= 400)
164 return _error->Error("Server refused our connection and said: %s",Msg.c_str());
165
166 // Send the user
167 if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
168 return false;
169 if (Tag >= 400)
170 return _error->Error("USER failed, server said: %s",Msg.c_str());
171
172 // Send the Password
173 if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
174 return false;
175 if (Tag >= 400)
176 return _error->Error("PASS failed, server said: %s",Msg.c_str());
177
178 // Enter passive mode
179 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
180 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
181 else
182 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
183 }
184 else
185 {
186 // Read the initial response
187 if (ReadResp(Tag,Msg) == false)
188 return false;
189 if (Tag >= 400)
190 return _error->Error("Server refused our connection and said: %s",Msg.c_str());
191
192 // Perform proxy script execution
193 Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
194 if (Opts == 0 || Opts->Child == 0)
195 return _error->Error("A proxy server was specified but no login "
196 "script, Acquire::ftp::ProxyLogin is empty.");
197 Opts = Opts->Child;
198
199 // Iterate over the entire login script
200 for (; Opts != 0; Opts = Opts->Next)
201 {
202 if (Opts->Value.empty() == true)
203 continue;
204
205 // Substitute the variables into the command
206 char SitePort[20];
207 if (ServerName.Port != 0)
208 sprintf(SitePort,"%u",ServerName.Port);
209 else
210 strcpy(SitePort,"21");
211 string Tmp = Opts->Value;
212 Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
213 Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
214 Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
215 Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
216 Tmp = SubstVar(Tmp,"$(SITE_PORT)",SitePort);
217 Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
218
219 // Send the command
220 if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
221 return false;
222 if (Tag >= 400)
223 return _error->Error("Login script command '%s' failed, server said: %s",Tmp.c_str(),Msg.c_str());
224 }
225
226 // Enter passive mode
227 TryPassive = false;
228 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
229 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
230 else
231 {
232 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
233 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
234 else
235 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
236 }
237 }
238
239 // Binary mode
240 if (WriteMsg(Tag,Msg,"TYPE I") == false)
241 return false;
242 if (Tag >= 400)
243 return _error->Error("TYPE failed, server said: %s",Msg.c_str());
244
245 return true;
246 }
247 /*}}}*/
248 // FTPConn::ReadLine - Read a line from the server /*{{{*/
249 // ---------------------------------------------------------------------
250 /* This performs a very simple buffered read. */
251 bool FTPConn::ReadLine(string &Text)
252 {
253 if (ServerFd == -1)
254 return false;
255
256 // Suck in a line
257 while (Len < sizeof(Buffer))
258 {
259 // Scan the buffer for a new line
260 for (unsigned int I = 0; I != Len; I++)
261 {
262 // Escape some special chars
263 if (Buffer[I] == 0)
264 Buffer[I] = '?';
265
266 // End of line?
267 if (Buffer[I] != '\n')
268 continue;
269
270 I++;
271 Text = string(Buffer,I);
272 memmove(Buffer,Buffer+I,Len - I);
273 Len -= I;
274 return true;
275 }
276
277 // Wait for some data..
278 if (WaitFd(ServerFd,false,TimeOut) == false)
279 {
280 Close();
281 return _error->Error("Connection timeout");
282 }
283
284 // Suck it back
285 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
286 if (Res <= 0)
287 {
288 Close();
289 return _error->Errno("read","Read error");
290 }
291 Len += Res;
292 }
293
294 return _error->Error("A response overflowed the buffer.");
295 }
296 /*}}}*/
297 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
298 // ---------------------------------------------------------------------
299 /* This reads a reply code from the server, it handles both p */
300 bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
301 {
302 // Grab the first line of the response
303 string Msg;
304 if (ReadLine(Msg) == false)
305 return false;
306
307 // Get the ID code
308 char *End;
309 Ret = strtol(Msg.c_str(),&End,10);
310 if (End - Msg.c_str() != 3)
311 return _error->Error("Protocol corruption");
312
313 // All done ?
314 Text = Msg.c_str()+4;
315 if (*End == ' ')
316 {
317 if (Debug == true)
318 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
319 return true;
320 }
321
322 if (*End != '-')
323 return _error->Error("Protocol corruption");
324
325 /* Okay, here we do the continued message trick. This is foolish, but
326 proftpd follows the protocol as specified and wu-ftpd doesn't, so
327 we filter. I wonder how many clients break if you use proftpd and
328 put a '- in the 3rd spot in the message? */
329 char Leader[4];
330 strncpy(Leader,Msg.c_str(),3);
331 Leader[3] = 0;
332 while (ReadLine(Msg) == true)
333 {
334 // Short, it must be using RFC continuation..
335 if (Msg.length() < 4)
336 {
337 Text += Msg;
338 continue;
339 }
340
341 // Oops, finished
342 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
343 {
344 Text += Msg.c_str()+4;
345 break;
346 }
347
348 // This message has the wu-ftpd style reply code prefixed
349 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
350 {
351 Text += Msg.c_str()+4;
352 continue;
353 }
354
355 // Must be RFC style prefixing
356 Text += Msg;
357 }
358
359 if (Debug == true && _error->PendingError() == false)
360 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
361
362 return !_error->PendingError();
363 }
364 /*}}}*/
365 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
366 // ---------------------------------------------------------------------
367 /* Simple printf like function.. */
368 bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
369 {
370 va_list args;
371 va_start(args,Fmt);
372
373 // sprintf the description
374 char S[400];
375 vsnprintf(S,sizeof(S) - 4,Fmt,args);
376 strcat(S,"\r\n");
377
378 if (Debug == true)
379 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
380
381 // Send it off
382 unsigned long Len = strlen(S);
383 unsigned long Start = 0;
384 while (Len != 0)
385 {
386 if (WaitFd(ServerFd,true,TimeOut) == false)
387 {
388 Close();
389 return _error->Error("Connection timeout");
390 }
391
392 int Res = write(ServerFd,S + Start,Len);
393 if (Res <= 0)
394 {
395 Close();
396 return _error->Errno("write","Write Error");
397 }
398
399 Len -= Res;
400 Start += Res;
401 }
402
403 return ReadResp(Ret,Text);
404 }
405 /*}}}*/
406 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
407 // ---------------------------------------------------------------------
408 /* Try to enter passive mode, the return code does not indicate if passive
409 mode could or could not be established, only if there was a fatal error.
410 Borrowed mostly from lftp. We have to enter passive mode every time
411 we make a data connection :| */
412 bool FTPConn::GoPasv()
413 {
414 // Try to enable pasv mode
415 unsigned int Tag;
416 string Msg;
417 if (WriteMsg(Tag,Msg,"PASV") == false)
418 return false;
419
420 // Unsupported function
421 string::size_type Pos = Msg.find('(');
422 if (Tag >= 400 || Pos == string::npos)
423 {
424 memset(&PasvAddr,0,sizeof(PasvAddr));
425 return true;
426 }
427
428 // Scan it
429 unsigned a0,a1,a2,a3,p0,p1;
430 if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
431 {
432 memset(&PasvAddr,0,sizeof(PasvAddr));
433 return true;
434 }
435
436 // lftp used this horrid byte order manipulation.. Ik.
437 PasvAddr.sin_family = AF_INET;
438 unsigned char *a;
439 unsigned char *p;
440 a = (unsigned char *)&PasvAddr.sin_addr;
441 p = (unsigned char *)&PasvAddr.sin_port;
442
443 // Some evil servers return 0 to mean their addr
444 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
445 {
446 PasvAddr.sin_addr = Peer.sin_addr;
447 }
448 else
449 {
450 a[0] = a0;
451 a[1] = a1;
452 a[2] = a2;
453 a[3] = a3;
454 }
455
456 p[0] = p0;
457 p[1] = p1;
458
459 return true;
460 }
461 /*}}}*/
462 // FTPConn::Size - Return the size of a file /*{{{*/
463 // ---------------------------------------------------------------------
464 /* Grab the file size from the server, 0 means no size or empty file */
465 bool FTPConn::Size(const char *Path,unsigned long &Size)
466 {
467 // Query the size
468 unsigned int Tag;
469 string Msg;
470 Size = 0;
471 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
472 return false;
473
474 char *End;
475 Size = strtol(Msg.c_str(),&End,10);
476 if (Tag >= 400 || End == Msg.c_str())
477 Size = 0;
478 return true;
479 }
480 /*}}}*/
481 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
482 // ---------------------------------------------------------------------
483 /* Like Size no error is returned if the command is not supported. If the
484 command fails then time is set to the current time of day to fool
485 date checks. */
486 bool FTPConn::ModTime(const char *Path, time_t &Time)
487 {
488 Time = time(&Time);
489
490 // Query the mod time
491 unsigned int Tag;
492 string Msg;
493 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
494 return false;
495 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
496 return true;
497
498 // Parse it
499 struct tm tm;
500 memset(&tm,0,sizeof(tm));
501 if (sscanf(Msg.c_str(),"%4d%2d%2d%2d%2d%2d",&tm.tm_year,&tm.tm_mon,
502 &tm.tm_mday,&tm.tm_hour,&tm.tm_min,&tm.tm_sec) != 6)
503 return true;
504
505 tm.tm_year -= 1900;
506 tm.tm_mon--;
507
508 /* We use timegm from the GNU C library, libapt-pkg will provide this
509 symbol if it does not exist */
510 Time = timegm(&tm);
511 return true;
512 }
513 /*}}}*/
514 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
515 // ---------------------------------------------------------------------
516 /* Create the data connection. Call FinalizeDataFd after this though.. */
517 bool FTPConn::CreateDataFd()
518 {
519 close(DataFd);
520 DataFd = -1;
521
522 // Attempt to enter passive mode.
523 if (TryPassive == true)
524 {
525 if (GoPasv() == false)
526 return false;
527
528 // Oops, didn't work out, don't bother trying again.
529 if (PasvAddr.sin_port == 0)
530 TryPassive = false;
531 }
532
533 // Passive mode?
534 if (PasvAddr.sin_port != 0)
535 {
536 // Get a socket
537 if ((DataFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
538 return _error->Errno("socket","Could not create a socket");
539
540 // Connect to the server
541 SetNonBlock(DataFd,true);
542 if (connect(DataFd,(sockaddr *)&PasvAddr,sizeof(PasvAddr)) < 0 &&
543 errno != EINPROGRESS)
544 return _error->Errno("socket","Could not create a socket");
545
546 /* This implements a timeout for connect by opening the connection
547 nonblocking */
548 if (WaitFd(ServerFd,true,TimeOut) == false)
549 return _error->Error("Could not connect data socket, connection timed out");
550 unsigned int Err;
551 unsigned int Len = sizeof(Err);
552 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
553 return _error->Errno("getsockopt","Failed");
554 if (Err != 0)
555 return _error->Error("Could not connect.");
556
557 return true;
558 }
559
560 // Port mode :<
561 close(DataListenFd);
562 DataListenFd = -1;
563
564 // Get a socket
565 if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
566 return _error->Errno("socket","Could not create a socket");
567
568 // Bind and listen
569 sockaddr_in Addr;
570 memset(&Addr,0,sizeof(Addr));
571 if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
572 return _error->Errno("bind","Could not bind a socket");
573 if (listen(DataListenFd,1) < 0)
574 return _error->Errno("listen","Could not listen on the socket");
575 SetNonBlock(DataListenFd,true);
576
577 // Determine the name to send to the remote
578 sockaddr_in Addr2;
579 socklen_t Jnk = sizeof(Addr);
580 if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
581 return _error->Errno("getsockname","Could not determine the socket's name");
582 Jnk = sizeof(Addr2);
583 if (getsockname(ServerFd,(sockaddr *)&Addr2,&Jnk) < 0)
584 return _error->Errno("getsockname","Could not determine the socket's name");
585
586 // This bit ripped from qftp
587 unsigned long badr = ntohl(*(unsigned long *)&Addr2.sin_addr);
588 unsigned long bp = ntohs(Addr.sin_port);
589
590 // Send the port command
591 unsigned int Tag;
592 string Msg;
593 if (WriteMsg(Tag,Msg,"PORT %d,%d,%d,%d,%d,%d",
594 (int) (badr >> 24) & 0xff, (int) (badr >> 16) & 0xff,
595 (int) (badr >> 8) & 0xff, (int) badr & 0xff,
596 (int) (bp >> 8) & 0xff, (int) bp & 0xff) == false)
597 return false;
598 if (Tag >= 400)
599 return _error->Error("Unable to send port command");
600
601 return true;
602 }
603 /*}}}*/
604 // FTPConn::Finalize - Complete the Data connection /*{{{*/
605 // ---------------------------------------------------------------------
606 /* If the connection is in port mode this waits for the other end to hook
607 up to us. */
608 bool FTPConn::Finalize()
609 {
610 // Passive mode? Do nothing
611 if (PasvAddr.sin_port != 0)
612 return true;
613
614 // Close any old socket..
615 close(DataFd);
616 DataFd = -1;
617
618 // Wait for someone to connect..
619 if (WaitFd(DataListenFd,false,TimeOut) == false)
620 return _error->Error("Data socket connect timed out");
621
622 // Accept the connection
623 struct sockaddr_in Addr;
624 socklen_t Len = sizeof(Addr);
625 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
626 if (DataFd < 0)
627 return _error->Errno("accept","Unable to accept connection");
628
629 close(DataListenFd);
630 DataListenFd = -1;
631
632 return true;
633 }
634 /*}}}*/
635 // FTPConn::Get - Get a file /*{{{*/
636 // ---------------------------------------------------------------------
637 /* This opens a data connection, sends REST and RETR and then
638 transfers the file over. */
639 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
640 MD5Summation &MD5,bool &Missing)
641 {
642 Missing = false;
643 if (CreateDataFd() == false)
644 return false;
645
646 unsigned int Tag;
647 string Msg;
648 if (Resume != 0)
649 {
650 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
651 return false;
652 if (Tag >= 400)
653 Resume = 0;
654 }
655
656 if (To.Truncate(Resume) == false)
657 return false;
658
659 if (To.Seek(0) == false)
660 return false;
661
662 if (Resume != 0)
663 {
664 if (MD5.AddFD(To.Fd(),Resume) == false)
665 {
666 _error->Errno("read","Problem hashing file");
667 return false;
668 }
669 }
670
671 // Send the get command
672 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
673 return false;
674
675 if (Tag >= 400)
676 {
677 if (Tag == 550)
678 Missing = true;
679 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
680 }
681
682 // Finish off the data connection
683 if (Finalize() == false)
684 return false;
685
686 // Copy loop
687 unsigned char Buffer[4096];
688 while (1)
689 {
690 // Wait for some data..
691 if (WaitFd(DataFd,false,TimeOut) == false)
692 {
693 Close();
694 return _error->Error("Data socket timed out");
695 }
696
697 // Read the data..
698 int Res = read(DataFd,Buffer,sizeof(Buffer));
699 if (Res == 0)
700 break;
701 if (Res < 0)
702 {
703 if (errno == EAGAIN)
704 continue;
705 break;
706 }
707
708 MD5.Add(Buffer,Res);
709 if (To.Write(Buffer,Res) == false)
710 {
711 Close();
712 return false;
713 }
714 }
715
716 // All done
717 close(DataFd);
718 DataFd = -1;
719
720 // Read the closing message from the server
721 if (ReadResp(Tag,Msg) == false)
722 return false;
723 if (Tag >= 400)
724 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
725 return true;
726 }
727 /*}}}*/
728
729 // FtpMethod::FtpMethod - Constructor /*{{{*/
730 // ---------------------------------------------------------------------
731 /* */
732 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
733 {
734 signal(SIGTERM,SigTerm);
735 signal(SIGINT,SigTerm);
736
737 Server = 0;
738 FailFd = -1;
739 }
740 /*}}}*/
741 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
742 // ---------------------------------------------------------------------
743 /* This closes and timestamps the open file. This is neccessary to get
744 resume behavoir on user abort */
745 void FtpMethod::SigTerm(int)
746 {
747 if (FailFd == -1)
748 exit(100);
749 close(FailFd);
750
751 // Timestamp
752 struct utimbuf UBuf;
753 UBuf.actime = FailTime;
754 UBuf.modtime = FailTime;
755 utime(FailFile.c_str(),&UBuf);
756
757 exit(100);
758 }
759 /*}}}*/
760 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
761 // ---------------------------------------------------------------------
762 /* We stash the desired pipeline depth */
763 bool FtpMethod::Configuration(string Message)
764 {
765 if (pkgAcqMethod::Configuration(Message) == false)
766 return false;
767
768 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
769 return true;
770 }
771 /*}}}*/
772 // FtpMethod::Fetch - Fetch a file /*{{{*/
773 // ---------------------------------------------------------------------
774 /* Fetch a single file, called by the base class.. */
775 bool FtpMethod::Fetch(FetchItem *Itm)
776 {
777 URI Get = Itm->Uri;
778 const char *File = Get.Path.c_str();
779 FetchResult Res;
780 Res.Filename = Itm->DestFile;
781 Res.IMSHit = false;
782
783 // Connect to the server
784 if (Server == 0 || Server->Comp(Get) == false)
785 {
786 delete Server;
787 Server = new FTPConn(Get);
788 }
789
790 // Could not connect is a transient error..
791 if (Server->Open(this) == false)
792 {
793 Server->Close();
794 Fail(true);
795 return true;
796 }
797
798 // Get the files information
799 Status("Query");
800 unsigned long Size;
801 if (Server->Size(File,Size) == false ||
802 Server->ModTime(File,FailTime) == false)
803 {
804 Fail(true);
805 return true;
806 }
807 Res.Size = Size;
808
809 // See if it is an IMS hit
810 if (Itm->LastModified == FailTime)
811 {
812 Res.Size = 0;
813 Res.IMSHit = true;
814 URIDone(Res);
815 return true;
816 }
817
818 // See if the file exists
819 struct stat Buf;
820 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
821 {
822 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
823 {
824 Res.Size = Buf.st_size;
825 Res.LastModified = Buf.st_mtime;
826 URIDone(Res);
827 return true;
828 }
829
830 // Resume?
831 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
832 Res.ResumePoint = Buf.st_size;
833 }
834
835 // Open the file
836 MD5Summation MD5;
837 {
838 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
839 if (_error->PendingError() == true)
840 return false;
841
842 URIStart(Res);
843
844 FailFile = Itm->DestFile;
845 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
846 FailFd = Fd.Fd();
847
848 bool Missing;
849 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
850 {
851 // If the file is missing we hard fail otherwise transient fail
852 if (Missing == true)
853 return false;
854 Fail(true);
855 return true;
856 }
857
858 Res.Size = Fd.Size();
859 }
860
861 Res.LastModified = FailTime;
862 Res.MD5Sum = MD5.Result();
863
864 // Timestamp
865 struct utimbuf UBuf;
866 time(&UBuf.actime);
867 UBuf.actime = FailTime;
868 UBuf.modtime = FailTime;
869 utime(Queue->DestFile.c_str(),&UBuf);
870 FailFd = -1;
871
872 URIDone(Res);
873
874 return true;
875 }
876 /*}}}*/
877
878 int main(int argc,const char *argv[])
879 {
880 /* See if we should be come the http client - we do this for http
881 proxy urls */
882 if (getenv("ftp_proxy") != 0)
883 {
884 URI Proxy = string(getenv("ftp_proxy"));
885 if (Proxy.Access == "http")
886 {
887 // Copy over the environment setting
888 char S[300];
889 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
890 putenv(S);
891
892 // Run the http method
893 string Path = flNotFile(argv[0]) + "/http";
894 execl(Path.c_str(),Path.c_str(),0);
895 cerr << "Unable to invoke " << Path << endl;
896 exit(100);
897 }
898 }
899
900 FtpMethod Mth;
901
902 return Mth.Run();
903 }