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