Moved time handling, fixed makefiles
[ntk/apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.18 1999/12/10 07:21:52 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 _error->Errno("read","Read error");
289 Close();
290 return false;
291 }
292 Len += Res;
293 }
294
295 return _error->Error("A response overflowed the buffer.");
296 }
297 /*}}}*/
298 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
299 // ---------------------------------------------------------------------
300 /* This reads a reply code from the server, it handles both p */
301 bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
302 {
303 // Grab the first line of the response
304 string Msg;
305 if (ReadLine(Msg) == false)
306 return false;
307
308 // Get the ID code
309 char *End;
310 Ret = strtol(Msg.c_str(),&End,10);
311 if (End - Msg.c_str() != 3)
312 return _error->Error("Protocol corruption");
313
314 // All done ?
315 Text = Msg.c_str()+4;
316 if (*End == ' ')
317 {
318 if (Debug == true)
319 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
320 return true;
321 }
322
323 if (*End != '-')
324 return _error->Error("Protocol corruption");
325
326 /* Okay, here we do the continued message trick. This is foolish, but
327 proftpd follows the protocol as specified and wu-ftpd doesn't, so
328 we filter. I wonder how many clients break if you use proftpd and
329 put a '- in the 3rd spot in the message? */
330 char Leader[4];
331 strncpy(Leader,Msg.c_str(),3);
332 Leader[3] = 0;
333 while (ReadLine(Msg) == true)
334 {
335 // Short, it must be using RFC continuation..
336 if (Msg.length() < 4)
337 {
338 Text += Msg;
339 continue;
340 }
341
342 // Oops, finished
343 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
344 {
345 Text += Msg.c_str()+4;
346 break;
347 }
348
349 // This message has the wu-ftpd style reply code prefixed
350 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
351 {
352 Text += Msg.c_str()+4;
353 continue;
354 }
355
356 // Must be RFC style prefixing
357 Text += Msg;
358 }
359
360 if (Debug == true && _error->PendingError() == false)
361 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
362
363 return !_error->PendingError();
364 }
365 /*}}}*/
366 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
367 // ---------------------------------------------------------------------
368 /* Simple printf like function.. */
369 bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
370 {
371 va_list args;
372 va_start(args,Fmt);
373
374 // sprintf the description
375 char S[400];
376 vsnprintf(S,sizeof(S) - 4,Fmt,args);
377 strcat(S,"\r\n");
378
379 if (Debug == true)
380 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
381
382 // Send it off
383 unsigned long Len = strlen(S);
384 unsigned long Start = 0;
385 while (Len != 0)
386 {
387 if (WaitFd(ServerFd,true,TimeOut) == false)
388 {
389 Close();
390 return _error->Error("Connection timeout");
391 }
392
393 int Res = write(ServerFd,S + Start,Len);
394 if (Res <= 0)
395 {
396 _error->Errno("write","Write Error");
397 Close();
398 return false;
399 }
400
401 Len -= Res;
402 Start += Res;
403 }
404
405 return ReadResp(Ret,Text);
406 }
407 /*}}}*/
408 // FTPConn::GoPasv - Enter Passive mode /*{{{*/
409 // ---------------------------------------------------------------------
410 /* Try to enter passive mode, the return code does not indicate if passive
411 mode could or could not be established, only if there was a fatal error.
412 Borrowed mostly from lftp. We have to enter passive mode every time
413 we make a data connection :| */
414 bool FTPConn::GoPasv()
415 {
416 // Try to enable pasv mode
417 unsigned int Tag;
418 string Msg;
419 if (WriteMsg(Tag,Msg,"PASV") == false)
420 return false;
421
422 // Unsupported function
423 string::size_type Pos = Msg.find('(');
424 if (Tag >= 400 || Pos == string::npos)
425 {
426 memset(&PasvAddr,0,sizeof(PasvAddr));
427 return true;
428 }
429
430 // Scan it
431 unsigned a0,a1,a2,a3,p0,p1;
432 if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
433 {
434 memset(&PasvAddr,0,sizeof(PasvAddr));
435 return true;
436 }
437
438 // lftp used this horrid byte order manipulation.. Ik.
439 PasvAddr.sin_family = AF_INET;
440 unsigned char *a;
441 unsigned char *p;
442 a = (unsigned char *)&PasvAddr.sin_addr;
443 p = (unsigned char *)&PasvAddr.sin_port;
444
445 // Some evil servers return 0 to mean their addr
446 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
447 {
448 PasvAddr.sin_addr = Peer.sin_addr;
449 }
450 else
451 {
452 a[0] = a0;
453 a[1] = a1;
454 a[2] = a2;
455 a[3] = a3;
456 }
457
458 p[0] = p0;
459 p[1] = p1;
460
461 return true;
462 }
463 /*}}}*/
464 // FTPConn::Size - Return the size of a file /*{{{*/
465 // ---------------------------------------------------------------------
466 /* Grab the file size from the server, 0 means no size or empty file */
467 bool FTPConn::Size(const char *Path,unsigned long &Size)
468 {
469 // Query the size
470 unsigned int Tag;
471 string Msg;
472 Size = 0;
473 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
474 return false;
475
476 char *End;
477 Size = strtol(Msg.c_str(),&End,10);
478 if (Tag >= 400 || End == Msg.c_str())
479 Size = 0;
480 return true;
481 }
482 /*}}}*/
483 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
484 // ---------------------------------------------------------------------
485 /* Like Size no error is returned if the command is not supported. If the
486 command fails then time is set to the current time of day to fool
487 date checks. */
488 bool FTPConn::ModTime(const char *Path, time_t &Time)
489 {
490 Time = time(&Time);
491
492 // Query the mod time
493 unsigned int Tag;
494 string Msg;
495 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
496 return false;
497 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
498 return true;
499
500 // Parse it
501 StrToTime(Msg,Time);
502 return true;
503 }
504 /*}}}*/
505 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
506 // ---------------------------------------------------------------------
507 /* Create the data connection. Call FinalizeDataFd after this though.. */
508 bool FTPConn::CreateDataFd()
509 {
510 close(DataFd);
511 DataFd = -1;
512
513 // Attempt to enter passive mode.
514 if (TryPassive == true)
515 {
516 if (GoPasv() == false)
517 return false;
518
519 // Oops, didn't work out, don't bother trying again.
520 if (PasvAddr.sin_port == 0)
521 TryPassive = false;
522 }
523
524 // Passive mode?
525 if (PasvAddr.sin_port != 0)
526 {
527 // Get a socket
528 if ((DataFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
529 return _error->Errno("socket","Could not create a socket");
530
531 // Connect to the server
532 SetNonBlock(DataFd,true);
533 if (connect(DataFd,(sockaddr *)&PasvAddr,sizeof(PasvAddr)) < 0 &&
534 errno != EINPROGRESS)
535 return _error->Errno("socket","Could not create a socket");
536
537 /* This implements a timeout for connect by opening the connection
538 nonblocking */
539 if (WaitFd(ServerFd,true,TimeOut) == false)
540 return _error->Error("Could not connect data socket, connection timed out");
541 unsigned int Err;
542 unsigned int Len = sizeof(Err);
543 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
544 return _error->Errno("getsockopt","Failed");
545 if (Err != 0)
546 return _error->Error("Could not connect.");
547
548 return true;
549 }
550
551 // Port mode :<
552 close(DataListenFd);
553 DataListenFd = -1;
554
555 // Get a socket
556 if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
557 return _error->Errno("socket","Could not create a socket");
558
559 // Bind and listen
560 sockaddr_in Addr;
561 memset(&Addr,0,sizeof(Addr));
562 if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
563 return _error->Errno("bind","Could not bind a socket");
564 if (listen(DataListenFd,1) < 0)
565 return _error->Errno("listen","Could not listen on the socket");
566 SetNonBlock(DataListenFd,true);
567
568 // Determine the name to send to the remote
569 sockaddr_in Addr2;
570 socklen_t Jnk = sizeof(Addr);
571 if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
572 return _error->Errno("getsockname","Could not determine the socket's name");
573 Jnk = sizeof(Addr2);
574 if (getsockname(ServerFd,(sockaddr *)&Addr2,&Jnk) < 0)
575 return _error->Errno("getsockname","Could not determine the socket's name");
576
577 // This bit ripped from qftp
578 unsigned long badr = ntohl(*(unsigned long *)&Addr2.sin_addr);
579 unsigned long bp = ntohs(Addr.sin_port);
580
581 // Send the port command
582 unsigned int Tag;
583 string Msg;
584 if (WriteMsg(Tag,Msg,"PORT %d,%d,%d,%d,%d,%d",
585 (int) (badr >> 24) & 0xff, (int) (badr >> 16) & 0xff,
586 (int) (badr >> 8) & 0xff, (int) badr & 0xff,
587 (int) (bp >> 8) & 0xff, (int) bp & 0xff) == false)
588 return false;
589 if (Tag >= 400)
590 return _error->Error("Unable to send port command");
591
592 return true;
593 }
594 /*}}}*/
595 // FTPConn::Finalize - Complete the Data connection /*{{{*/
596 // ---------------------------------------------------------------------
597 /* If the connection is in port mode this waits for the other end to hook
598 up to us. */
599 bool FTPConn::Finalize()
600 {
601 // Passive mode? Do nothing
602 if (PasvAddr.sin_port != 0)
603 return true;
604
605 // Close any old socket..
606 close(DataFd);
607 DataFd = -1;
608
609 // Wait for someone to connect..
610 if (WaitFd(DataListenFd,false,TimeOut) == false)
611 return _error->Error("Data socket connect timed out");
612
613 // Accept the connection
614 struct sockaddr_in Addr;
615 socklen_t Len = sizeof(Addr);
616 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
617 if (DataFd < 0)
618 return _error->Errno("accept","Unable to accept connection");
619
620 close(DataListenFd);
621 DataListenFd = -1;
622
623 return true;
624 }
625 /*}}}*/
626 // FTPConn::Get - Get a file /*{{{*/
627 // ---------------------------------------------------------------------
628 /* This opens a data connection, sends REST and RETR and then
629 transfers the file over. */
630 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
631 MD5Summation &MD5,bool &Missing)
632 {
633 Missing = false;
634 if (CreateDataFd() == false)
635 return false;
636
637 unsigned int Tag;
638 string Msg;
639 if (Resume != 0)
640 {
641 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
642 return false;
643 if (Tag >= 400)
644 Resume = 0;
645 }
646
647 if (To.Truncate(Resume) == false)
648 return false;
649
650 if (To.Seek(0) == false)
651 return false;
652
653 if (Resume != 0)
654 {
655 if (MD5.AddFD(To.Fd(),Resume) == false)
656 {
657 _error->Errno("read","Problem hashing file");
658 return false;
659 }
660 }
661
662 // Send the get command
663 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
664 return false;
665
666 if (Tag >= 400)
667 {
668 if (Tag == 550)
669 Missing = true;
670 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
671 }
672
673 // Finish off the data connection
674 if (Finalize() == false)
675 return false;
676
677 // Copy loop
678 unsigned char Buffer[4096];
679 while (1)
680 {
681 // Wait for some data..
682 if (WaitFd(DataFd,false,TimeOut) == false)
683 {
684 Close();
685 return _error->Error("Data socket timed out");
686 }
687
688 // Read the data..
689 int Res = read(DataFd,Buffer,sizeof(Buffer));
690 if (Res == 0)
691 break;
692 if (Res < 0)
693 {
694 if (errno == EAGAIN)
695 continue;
696 break;
697 }
698
699 MD5.Add(Buffer,Res);
700 if (To.Write(Buffer,Res) == false)
701 {
702 Close();
703 return false;
704 }
705 }
706
707 // All done
708 close(DataFd);
709 DataFd = -1;
710
711 // Read the closing message from the server
712 if (ReadResp(Tag,Msg) == false)
713 return false;
714 if (Tag >= 400)
715 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
716 return true;
717 }
718 /*}}}*/
719
720 // FtpMethod::FtpMethod - Constructor /*{{{*/
721 // ---------------------------------------------------------------------
722 /* */
723 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
724 {
725 signal(SIGTERM,SigTerm);
726 signal(SIGINT,SigTerm);
727
728 Server = 0;
729 FailFd = -1;
730 }
731 /*}}}*/
732 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
733 // ---------------------------------------------------------------------
734 /* This closes and timestamps the open file. This is neccessary to get
735 resume behavoir on user abort */
736 void FtpMethod::SigTerm(int)
737 {
738 if (FailFd == -1)
739 _exit(100);
740 close(FailFd);
741
742 // Timestamp
743 struct utimbuf UBuf;
744 UBuf.actime = FailTime;
745 UBuf.modtime = FailTime;
746 utime(FailFile.c_str(),&UBuf);
747
748 _exit(100);
749 }
750 /*}}}*/
751 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
752 // ---------------------------------------------------------------------
753 /* We stash the desired pipeline depth */
754 bool FtpMethod::Configuration(string Message)
755 {
756 if (pkgAcqMethod::Configuration(Message) == false)
757 return false;
758
759 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
760 return true;
761 }
762 /*}}}*/
763 // FtpMethod::Fetch - Fetch a file /*{{{*/
764 // ---------------------------------------------------------------------
765 /* Fetch a single file, called by the base class.. */
766 bool FtpMethod::Fetch(FetchItem *Itm)
767 {
768 URI Get = Itm->Uri;
769 const char *File = Get.Path.c_str();
770 FetchResult Res;
771 Res.Filename = Itm->DestFile;
772 Res.IMSHit = false;
773
774 // Connect to the server
775 if (Server == 0 || Server->Comp(Get) == false)
776 {
777 delete Server;
778 Server = new FTPConn(Get);
779 }
780
781 // Could not connect is a transient error..
782 if (Server->Open(this) == false)
783 {
784 Server->Close();
785 Fail(true);
786 return true;
787 }
788
789 // Get the files information
790 Status("Query");
791 unsigned long Size;
792 if (Server->Size(File,Size) == false ||
793 Server->ModTime(File,FailTime) == false)
794 {
795 Fail(true);
796 return true;
797 }
798 Res.Size = Size;
799
800 // See if it is an IMS hit
801 if (Itm->LastModified == FailTime)
802 {
803 Res.Size = 0;
804 Res.IMSHit = true;
805 URIDone(Res);
806 return true;
807 }
808
809 // See if the file exists
810 struct stat Buf;
811 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
812 {
813 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
814 {
815 Res.Size = Buf.st_size;
816 Res.LastModified = Buf.st_mtime;
817 URIDone(Res);
818 return true;
819 }
820
821 // Resume?
822 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
823 Res.ResumePoint = Buf.st_size;
824 }
825
826 // Open the file
827 MD5Summation MD5;
828 {
829 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
830 if (_error->PendingError() == true)
831 return false;
832
833 URIStart(Res);
834
835 FailFile = Itm->DestFile;
836 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
837 FailFd = Fd.Fd();
838
839 bool Missing;
840 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
841 {
842 Fd.Close();
843
844 // Timestamp
845 struct utimbuf UBuf;
846 time(&UBuf.actime);
847 UBuf.actime = FailTime;
848 UBuf.modtime = FailTime;
849 utime(FailFile.c_str(),&UBuf);
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 }