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