Reorderd error handling
[ntk/apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
f93d1355 3// $Id: ftp.cc,v 1.17 1999/12/09 03:45:56 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 287 {
f93d1355 288 _error->Errno("read","Read error");
ce0ae89a 289 Close();
f93d1355 290 return false;
ce0ae89a 291 }
30b30ec1
AL
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 */
301bool 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)
ce0ae89a 319 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
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)
ce0ae89a 361 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
362
363 return !_error->PendingError();
364}
365 /*}}}*/
366// FTPConn::WriteMsg - Send a message to the server /*{{{*/
367// ---------------------------------------------------------------------
368/* Simple printf like function.. */
369bool 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)
ce0ae89a 380 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
30b30ec1
AL
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)
ce0ae89a
AL
388 {
389 Close();
30b30ec1 390 return _error->Error("Connection timeout");
ce0ae89a 391 }
30b30ec1
AL
392
393 int Res = write(ServerFd,S + Start,Len);
394 if (Res <= 0)
ce0ae89a 395 {
f93d1355 396 _error->Errno("write","Write Error");
ce0ae89a 397 Close();
f93d1355 398 return false;
ce0ae89a
AL
399 }
400
30b30ec1
AL
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 :| */
414bool 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 */
ce0ae89a 467bool FTPConn::Size(const char *Path,unsigned long &Size)
30b30ec1
AL
468{
469 // Query the size
470 unsigned int Tag;
471 string Msg;
ce0ae89a 472 Size = 0;
30b30ec1
AL
473 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
474 return false;
475
476 char *End;
ce0ae89a 477 Size = strtol(Msg.c_str(),&End,10);
30b30ec1 478 if (Tag >= 400 || End == Msg.c_str())
ce0ae89a
AL
479 Size = 0;
480 return true;
30b30ec1
AL
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. */
488bool 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.. */
519bool 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
6d13bbca 549 nonblocking */
30b30ec1
AL
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 :<
2de438e7
AL
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");
30b30ec1 569
2de438e7 570 // Bind and listen
30b30ec1 571 sockaddr_in Addr;
2de438e7
AL
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
30b30ec1
AL
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. */
610bool 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
2de438e7
AL
631 close(DataListenFd);
632 DataListenFd = -1;
633
30b30ec1
AL
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. */
ce0ae89a
AL
641bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
642 MD5Summation &MD5,bool &Missing)
30b30ec1 643{
ce0ae89a 644 Missing = false;
30b30ec1
AL
645 if (CreateDataFd() == false)
646 return false;
647
648 unsigned int Tag;
ce0ae89a 649 string Msg;
30b30ec1
AL
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;
10861bb5
AL
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 }
30b30ec1
AL
672
673 // Send the get command
674 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
675 return false;
676
677 if (Tag >= 400)
ce0ae89a
AL
678 {
679 if (Tag == 550)
680 Missing = true;
30b30ec1 681 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
ce0ae89a 682 }
30b30ec1
AL
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)
25dbb396
AL
694 {
695 Close();
696 return _error->Error("Data socket timed out");
697 }
698
30b30ec1
AL
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 }
10861bb5
AL
709
710 MD5.Add(Buffer,Res);
30b30ec1 711 if (To.Write(Buffer,Res) == false)
25dbb396
AL
712 {
713 Close();
30b30ec1 714 return false;
25dbb396 715 }
30b30ec1
AL
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
ce0ae89a
AL
731// FtpMethod::FtpMethod - Constructor /*{{{*/
732// ---------------------------------------------------------------------
733/* */
734FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
30b30ec1 735{
ce0ae89a
AL
736 signal(SIGTERM,SigTerm);
737 signal(SIGINT,SigTerm);
30b30ec1 738
ce0ae89a
AL
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 */
747void FtpMethod::SigTerm(int)
748{
749 if (FailFd == -1)
ffe9323a 750 _exit(100);
ce0ae89a
AL
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
ffe9323a 759 _exit(100);
ce0ae89a
AL
760}
761 /*}}}*/
762// FtpMethod::Configuration - Handle a configuration message /*{{{*/
763// ---------------------------------------------------------------------
764/* We stash the desired pipeline depth */
765bool 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// ---------------------------------------------------------------------
10861bb5 776/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
777bool 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)
30b30ec1 787 {
ce0ae89a
AL
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 {
b3e53cec 795 Server->Close();
ce0ae89a
AL
796 Fail(true);
797 return true;
798 }
30b30ec1 799
ce0ae89a 800 // Get the files information
f26f6d38 801 Status("Query");
ce0ae89a
AL
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)
30b30ec1 825 {
ce0ae89a
AL
826 Res.Size = Buf.st_size;
827 Res.LastModified = Buf.st_mtime;
828 URIDone(Res);
829 return true;
30b30ec1
AL
830 }
831
ce0ae89a 832 // Resume?
10861bb5 833 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
ce0ae89a
AL
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)
30b30ec1 852 {
0dfc0829
AL
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
ce0ae89a
AL
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;
30b30ec1 867 }
ce0ae89a
AL
868
869 Res.Size = Fd.Size();
30b30ec1
AL
870 }
871
ce0ae89a
AL
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
d4489322 889int main(int argc,const char *argv[])
ce0ae89a 890{
d4489322
AL
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";
f436bdc5 905 execl(Path.c_str(),Path.c_str(),0);
d4489322
AL
906 cerr << "Unable to invoke " << Path << endl;
907 exit(100);
908 }
909 }
910
ce0ae89a
AL
911 FtpMethod Mth;
912
913 return Mth.Run();
30b30ec1 914}