More touch ups
[ntk/apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
0dfc0829 3// $Id: ftp.cc,v 1.15 1999/09/05 05:41:41 jgg Exp $
30b30ec1
AL
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.
934b6582
AL
12
13 RFC 2428 describes the IPv6 FTP behavior
14
30b30ec1
AL
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>
30b30ec1
AL
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
6d13bbca 38#include "rfc2553emu.h"
0837bd25 39#include "connect.h"
ce0ae89a 40#include "ftp.h"
30b30ec1
AL
41 /*}}}*/
42
43unsigned long TimeOut = 120;
44URI Proxy;
ce0ae89a
AL
45string FtpMethod::FailFile;
46int FtpMethod::FailFd = -1;
47time_t FtpMethod::FailTime = 0;
30b30ec1
AL
48
49// FTPConn::FTPConn - Constructor /*{{{*/
50// ---------------------------------------------------------------------
51/* */
52FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
53 DataListenFd(-1), ServerName(Srv)
54{
ce0ae89a 55 Debug = _config->FindB("Debug::Acquire::Ftp",false);
30b30ec1
AL
56 memset(&PasvAddr,0,sizeof(PasvAddr));
57}
58 /*}}}*/
59// FTPConn::~FTPConn - Destructor /*{{{*/
60// ---------------------------------------------------------------------
61/* */
62FTPConn::~FTPConn()
63{
64 Close();
65}
66 /*}}}*/
67// FTPConn::Close - Close down the connection /*{{{*/
68// ---------------------------------------------------------------------
69/* Just tear down the socket and data socket */
70void 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. */
ce0ae89a 85bool FTPConn::Open(pkgAcqMethod *Owner)
30b30ec1
AL
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
6d13bbca 112 int Port = 0;
30b30ec1
AL
113 string Host;
114 if (Proxy.empty() == true)
115 {
116 if (ServerName.Port != 0)
117 Port = ServerName.Port;
30b30ec1
AL
118 Host = ServerName.Host;
119 }
120 else
121 {
122 if (Proxy.Port != 0)
123 Port = Proxy.Port;
124 Host = Proxy.Host;
125 }
6d13bbca 126
0837bd25 127 // Connect to the remote server
9505213b 128 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
0837bd25
AL
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");
30b30ec1 133
ce0ae89a 134 Owner->Status("Logging in");
30b30ec1
AL
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. */
142bool 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
10861bb5
AL
179 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
180 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1 181 else
10861bb5 182 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
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];
ce0ae89a
AL
207 if (ServerName.Port != 0)
208 sprintf(SitePort,"%u",ServerName.Port);
209 else
10861bb5 210 strcpy(SitePort,"21");
30b30ec1
AL
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;
10861bb5
AL
228 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
229 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1
AL
230 else
231 {
10861bb5
AL
232 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
233 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
30b30ec1 234 else
10861bb5 235 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
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. */
251bool FTPConn::ReadLine(string &Text)
252{
2e90f6e0
AL
253 if (ServerFd == -1)
254 return false;
255
30b30ec1
AL
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)
ce0ae89a
AL
279 {
280 Close();
30b30ec1 281 return _error->Error("Connection timeout");
ce0ae89a 282 }
30b30ec1
AL
283
284 // Suck it back
2e90f6e0 285 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
30b30ec1 286 if (Res <= 0)
ce0ae89a
AL
287 {
288 Close();
30b30ec1 289 return _error->Errno("read","Read error");
ce0ae89a 290 }
30b30ec1
AL
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 */
300bool 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)
ce0ae89a 318 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
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)
ce0ae89a 360 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
361
362 return !_error->PendingError();
363}
364 /*}}}*/
365// FTPConn::WriteMsg - Send a message to the server /*{{{*/
366// ---------------------------------------------------------------------
367/* Simple printf like function.. */
368bool 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)
ce0ae89a 379 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
30b30ec1
AL
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)
ce0ae89a
AL
387 {
388 Close();
30b30ec1 389 return _error->Error("Connection timeout");
ce0ae89a 390 }
30b30ec1
AL
391
392 int Res = write(ServerFd,S + Start,Len);
393 if (Res <= 0)
ce0ae89a
AL
394 {
395 Close();
30b30ec1 396 return _error->Errno("write","Write Error");
ce0ae89a
AL
397 }
398
30b30ec1
AL
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 :| */
412bool 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 */
ce0ae89a 465bool FTPConn::Size(const char *Path,unsigned long &Size)
30b30ec1
AL
466{
467 // Query the size
468 unsigned int Tag;
469 string Msg;
ce0ae89a 470 Size = 0;
30b30ec1
AL
471 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
472 return false;
473
474 char *End;
ce0ae89a 475 Size = strtol(Msg.c_str(),&End,10);
30b30ec1 476 if (Tag >= 400 || End == Msg.c_str())
ce0ae89a
AL
477 Size = 0;
478 return true;
30b30ec1
AL
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. */
486bool 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.. */
517bool 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
6d13bbca 547 nonblocking */
30b30ec1
AL
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 :<
2de438e7
AL
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");
30b30ec1 567
2de438e7 568 // Bind and listen
30b30ec1 569 sockaddr_in Addr;
2de438e7
AL
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
30b30ec1
AL
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. */
608bool 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
2de438e7
AL
629 close(DataListenFd);
630 DataListenFd = -1;
631
30b30ec1
AL
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. */
ce0ae89a
AL
639bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
640 MD5Summation &MD5,bool &Missing)
30b30ec1 641{
ce0ae89a 642 Missing = false;
30b30ec1
AL
643 if (CreateDataFd() == false)
644 return false;
645
646 unsigned int Tag;
ce0ae89a 647 string Msg;
30b30ec1
AL
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;
10861bb5
AL
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 }
30b30ec1
AL
670
671 // Send the get command
672 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
673 return false;
674
675 if (Tag >= 400)
ce0ae89a
AL
676 {
677 if (Tag == 550)
678 Missing = true;
30b30ec1 679 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
ce0ae89a 680 }
30b30ec1
AL
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)
25dbb396
AL
692 {
693 Close();
694 return _error->Error("Data socket timed out");
695 }
696
30b30ec1
AL
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 }
10861bb5
AL
707
708 MD5.Add(Buffer,Res);
30b30ec1 709 if (To.Write(Buffer,Res) == false)
25dbb396
AL
710 {
711 Close();
30b30ec1 712 return false;
25dbb396 713 }
30b30ec1
AL
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
ce0ae89a
AL
729// FtpMethod::FtpMethod - Constructor /*{{{*/
730// ---------------------------------------------------------------------
731/* */
732FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
30b30ec1 733{
ce0ae89a
AL
734 signal(SIGTERM,SigTerm);
735 signal(SIGINT,SigTerm);
30b30ec1 736
ce0ae89a
AL
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 */
745void 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 */
763bool 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// ---------------------------------------------------------------------
10861bb5 774/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
775bool 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)
30b30ec1 785 {
ce0ae89a
AL
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 {
b3e53cec 793 Server->Close();
ce0ae89a
AL
794 Fail(true);
795 return true;
796 }
30b30ec1 797
ce0ae89a 798 // Get the files information
f26f6d38 799 Status("Query");
ce0ae89a
AL
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)
30b30ec1 823 {
ce0ae89a
AL
824 Res.Size = Buf.st_size;
825 Res.LastModified = Buf.st_mtime;
826 URIDone(Res);
827 return true;
30b30ec1
AL
828 }
829
ce0ae89a 830 // Resume?
10861bb5 831 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
ce0ae89a
AL
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)
30b30ec1 850 {
0dfc0829
AL
851 Fd.Close();
852
853 // Timestamp
854 struct utimbuf UBuf;
855 time(&UBuf.actime);
856 UBuf.actime = FailTime;
857 UBuf.modtime = FailTime;
858 utime(FailFile.c_str(),&UBuf);
859
ce0ae89a
AL
860 // If the file is missing we hard fail otherwise transient fail
861 if (Missing == true)
862 return false;
863 Fail(true);
864 return true;
30b30ec1 865 }
ce0ae89a
AL
866
867 Res.Size = Fd.Size();
30b30ec1
AL
868 }
869
ce0ae89a
AL
870 Res.LastModified = FailTime;
871 Res.MD5Sum = MD5.Result();
872
873 // Timestamp
874 struct utimbuf UBuf;
875 time(&UBuf.actime);
876 UBuf.actime = FailTime;
877 UBuf.modtime = FailTime;
878 utime(Queue->DestFile.c_str(),&UBuf);
879 FailFd = -1;
880
881 URIDone(Res);
882
883 return true;
884}
885 /*}}}*/
886
d4489322 887int main(int argc,const char *argv[])
ce0ae89a 888{
d4489322
AL
889 /* See if we should be come the http client - we do this for http
890 proxy urls */
891 if (getenv("ftp_proxy") != 0)
892 {
893 URI Proxy = string(getenv("ftp_proxy"));
894 if (Proxy.Access == "http")
895 {
896 // Copy over the environment setting
897 char S[300];
898 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
899 putenv(S);
900
901 // Run the http method
902 string Path = flNotFile(argv[0]) + "/http";
f436bdc5 903 execl(Path.c_str(),Path.c_str(),0);
d4489322
AL
904 cerr << "Unable to invoke " << Path << endl;
905 exit(100);
906 }
907 }
908
ce0ae89a
AL
909 FtpMethod Mth;
910
911 return Mth.Run();
30b30ec1 912}