]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
a7fa8323303bb924608b8f29fe7ff4bff610877c
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.17 1999/12/09 03:45: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 _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 struct tm tm;
502 memset(&tm,0,sizeof(tm));
503 if (sscanf(Msg.c_str(),"%4d%2d%2d%2d%2d%2d",&tm.tm_year,&tm.tm_mon,
504 &tm.tm_mday,&tm.tm_hour,&tm.tm_min,&tm.tm_sec) != 6)
505 return true;
506
507 tm.tm_year -= 1900;
508 tm.tm_mon--;
509
510 /* We use timegm from the GNU C library, libapt-pkg will provide this
511 symbol if it does not exist */
512 Time = timegm(&tm);
513 return true;
514 }
515 /*}}}*/
516 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
517 // ---------------------------------------------------------------------
518 /* Create the data connection. Call FinalizeDataFd after this though.. */
519 bool FTPConn::CreateDataFd()
520 {
521 close(DataFd);
522 DataFd = -1;
523
524 // Attempt to enter passive mode.
525 if (TryPassive == true)
526 {
527 if (GoPasv() == false)
528 return false;
529
530 // Oops, didn't work out, don't bother trying again.
531 if (PasvAddr.sin_port == 0)
532 TryPassive = false;
533 }
534
535 // Passive mode?
536 if (PasvAddr.sin_port != 0)
537 {
538 // Get a socket
539 if ((DataFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
540 return _error->Errno("socket","Could not create a socket");
541
542 // Connect to the server
543 SetNonBlock(DataFd,true);
544 if (connect(DataFd,(sockaddr *)&PasvAddr,sizeof(PasvAddr)) < 0 &&
545 errno != EINPROGRESS)
546 return _error->Errno("socket","Could not create a socket");
547
548 /* This implements a timeout for connect by opening the connection
549 nonblocking */
550 if (WaitFd(ServerFd,true,TimeOut) == false)
551 return _error->Error("Could not connect data socket, connection timed out");
552 unsigned int Err;
553 unsigned int Len = sizeof(Err);
554 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
555 return _error->Errno("getsockopt","Failed");
556 if (Err != 0)
557 return _error->Error("Could not connect.");
558
559 return true;
560 }
561
562 // Port mode :<
563 close(DataListenFd);
564 DataListenFd = -1;
565
566 // Get a socket
567 if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
568 return _error->Errno("socket","Could not create a socket");
569
570 // Bind and listen
571 sockaddr_in Addr;
572 memset(&Addr,0,sizeof(Addr));
573 if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
574 return _error->Errno("bind","Could not bind a socket");
575 if (listen(DataListenFd,1) < 0)
576 return _error->Errno("listen","Could not listen on the socket");
577 SetNonBlock(DataListenFd,true);
578
579 // Determine the name to send to the remote
580 sockaddr_in Addr2;
581 socklen_t Jnk = sizeof(Addr);
582 if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
583 return _error->Errno("getsockname","Could not determine the socket's name");
584 Jnk = sizeof(Addr2);
585 if (getsockname(ServerFd,(sockaddr *)&Addr2,&Jnk) < 0)
586 return _error->Errno("getsockname","Could not determine the socket's name");
587
588 // This bit ripped from qftp
589 unsigned long badr = ntohl(*(unsigned long *)&Addr2.sin_addr);
590 unsigned long bp = ntohs(Addr.sin_port);
591
592 // Send the port command
593 unsigned int Tag;
594 string Msg;
595 if (WriteMsg(Tag,Msg,"PORT %d,%d,%d,%d,%d,%d",
596 (int) (badr >> 24) & 0xff, (int) (badr >> 16) & 0xff,
597 (int) (badr >> 8) & 0xff, (int) badr & 0xff,
598 (int) (bp >> 8) & 0xff, (int) bp & 0xff) == false)
599 return false;
600 if (Tag >= 400)
601 return _error->Error("Unable to send port command");
602
603 return true;
604 }
605 /*}}}*/
606 // FTPConn::Finalize - Complete the Data connection /*{{{*/
607 // ---------------------------------------------------------------------
608 /* If the connection is in port mode this waits for the other end to hook
609 up to us. */
610 bool FTPConn::Finalize()
611 {
612 // Passive mode? Do nothing
613 if (PasvAddr.sin_port != 0)
614 return true;
615
616 // Close any old socket..
617 close(DataFd);
618 DataFd = -1;
619
620 // Wait for someone to connect..
621 if (WaitFd(DataListenFd,false,TimeOut) == false)
622 return _error->Error("Data socket connect timed out");
623
624 // Accept the connection
625 struct sockaddr_in Addr;
626 socklen_t Len = sizeof(Addr);
627 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
628 if (DataFd < 0)
629 return _error->Errno("accept","Unable to accept connection");
630
631 close(DataListenFd);
632 DataListenFd = -1;
633
634 return true;
635 }
636 /*}}}*/
637 // FTPConn::Get - Get a file /*{{{*/
638 // ---------------------------------------------------------------------
639 /* This opens a data connection, sends REST and RETR and then
640 transfers the file over. */
641 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
642 MD5Summation &MD5,bool &Missing)
643 {
644 Missing = false;
645 if (CreateDataFd() == false)
646 return false;
647
648 unsigned int Tag;
649 string Msg;
650 if (Resume != 0)
651 {
652 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
653 return false;
654 if (Tag >= 400)
655 Resume = 0;
656 }
657
658 if (To.Truncate(Resume) == false)
659 return false;
660
661 if (To.Seek(0) == false)
662 return false;
663
664 if (Resume != 0)
665 {
666 if (MD5.AddFD(To.Fd(),Resume) == false)
667 {
668 _error->Errno("read","Problem hashing file");
669 return false;
670 }
671 }
672
673 // Send the get command
674 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
675 return false;
676
677 if (Tag >= 400)
678 {
679 if (Tag == 550)
680 Missing = true;
681 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
682 }
683
684 // Finish off the data connection
685 if (Finalize() == false)
686 return false;
687
688 // Copy loop
689 unsigned char Buffer[4096];
690 while (1)
691 {
692 // Wait for some data..
693 if (WaitFd(DataFd,false,TimeOut) == false)
694 {
695 Close();
696 return _error->Error("Data socket timed out");
697 }
698
699 // Read the data..
700 int Res = read(DataFd,Buffer,sizeof(Buffer));
701 if (Res == 0)
702 break;
703 if (Res < 0)
704 {
705 if (errno == EAGAIN)
706 continue;
707 break;
708 }
709
710 MD5.Add(Buffer,Res);
711 if (To.Write(Buffer,Res) == false)
712 {
713 Close();
714 return false;
715 }
716 }
717
718 // All done
719 close(DataFd);
720 DataFd = -1;
721
722 // Read the closing message from the server
723 if (ReadResp(Tag,Msg) == false)
724 return false;
725 if (Tag >= 400)
726 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
727 return true;
728 }
729 /*}}}*/
730
731 // FtpMethod::FtpMethod - Constructor /*{{{*/
732 // ---------------------------------------------------------------------
733 /* */
734 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
735 {
736 signal(SIGTERM,SigTerm);
737 signal(SIGINT,SigTerm);
738
739 Server = 0;
740 FailFd = -1;
741 }
742 /*}}}*/
743 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
744 // ---------------------------------------------------------------------
745 /* This closes and timestamps the open file. This is neccessary to get
746 resume behavoir on user abort */
747 void FtpMethod::SigTerm(int)
748 {
749 if (FailFd == -1)
750 _exit(100);
751 close(FailFd);
752
753 // Timestamp
754 struct utimbuf UBuf;
755 UBuf.actime = FailTime;
756 UBuf.modtime = FailTime;
757 utime(FailFile.c_str(),&UBuf);
758
759 _exit(100);
760 }
761 /*}}}*/
762 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
763 // ---------------------------------------------------------------------
764 /* We stash the desired pipeline depth */
765 bool FtpMethod::Configuration(string Message)
766 {
767 if (pkgAcqMethod::Configuration(Message) == false)
768 return false;
769
770 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
771 return true;
772 }
773 /*}}}*/
774 // FtpMethod::Fetch - Fetch a file /*{{{*/
775 // ---------------------------------------------------------------------
776 /* Fetch a single file, called by the base class.. */
777 bool FtpMethod::Fetch(FetchItem *Itm)
778 {
779 URI Get = Itm->Uri;
780 const char *File = Get.Path.c_str();
781 FetchResult Res;
782 Res.Filename = Itm->DestFile;
783 Res.IMSHit = false;
784
785 // Connect to the server
786 if (Server == 0 || Server->Comp(Get) == false)
787 {
788 delete Server;
789 Server = new FTPConn(Get);
790 }
791
792 // Could not connect is a transient error..
793 if (Server->Open(this) == false)
794 {
795 Server->Close();
796 Fail(true);
797 return true;
798 }
799
800 // Get the files information
801 Status("Query");
802 unsigned long Size;
803 if (Server->Size(File,Size) == false ||
804 Server->ModTime(File,FailTime) == false)
805 {
806 Fail(true);
807 return true;
808 }
809 Res.Size = Size;
810
811 // See if it is an IMS hit
812 if (Itm->LastModified == FailTime)
813 {
814 Res.Size = 0;
815 Res.IMSHit = true;
816 URIDone(Res);
817 return true;
818 }
819
820 // See if the file exists
821 struct stat Buf;
822 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
823 {
824 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
825 {
826 Res.Size = Buf.st_size;
827 Res.LastModified = Buf.st_mtime;
828 URIDone(Res);
829 return true;
830 }
831
832 // Resume?
833 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
834 Res.ResumePoint = Buf.st_size;
835 }
836
837 // Open the file
838 MD5Summation MD5;
839 {
840 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
841 if (_error->PendingError() == true)
842 return false;
843
844 URIStart(Res);
845
846 FailFile = Itm->DestFile;
847 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
848 FailFd = Fd.Fd();
849
850 bool Missing;
851 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
852 {
853 Fd.Close();
854
855 // Timestamp
856 struct utimbuf UBuf;
857 time(&UBuf.actime);
858 UBuf.actime = FailTime;
859 UBuf.modtime = FailTime;
860 utime(FailFile.c_str(),&UBuf);
861
862 // If the file is missing we hard fail otherwise transient fail
863 if (Missing == true)
864 return false;
865 Fail(true);
866 return true;
867 }
868
869 Res.Size = Fd.Size();
870 }
871
872 Res.LastModified = FailTime;
873 Res.MD5Sum = MD5.Result();
874
875 // Timestamp
876 struct utimbuf UBuf;
877 time(&UBuf.actime);
878 UBuf.actime = FailTime;
879 UBuf.modtime = FailTime;
880 utime(Queue->DestFile.c_str(),&UBuf);
881 FailFd = -1;
882
883 URIDone(Res);
884
885 return true;
886 }
887 /*}}}*/
888
889 int main(int argc,const char *argv[])
890 {
891 /* See if we should be come the http client - we do this for http
892 proxy urls */
893 if (getenv("ftp_proxy") != 0)
894 {
895 URI Proxy = string(getenv("ftp_proxy"));
896 if (Proxy.Access == "http")
897 {
898 // Copy over the environment setting
899 char S[300];
900 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
901 putenv(S);
902
903 // Run the http method
904 string Path = flNotFile(argv[0]) + "/http";
905 execl(Path.c_str(),Path.c_str(),0);
906 cerr << "Unable to invoke " << Path << endl;
907 exit(100);
908 }
909 }
910
911 FtpMethod Mth;
912
913 return Mth.Run();
914 }