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