mirror of
https://github.com/ukui/apt.git
synced 2026-03-09 09:35:45 -07:00
Every method opts in to getting the encoded URI passed along while keeping compat in case we are operated by an older acquire system. Effectively this is just a change for the http-based methods as the others just decode the URI as they work with files directly.
1189 lines
32 KiB
C++
1189 lines
32 KiB
C++
// -*- mode: cpp; mode: fold -*-
|
|
// Description /*{{{*/
|
|
/* ######################################################################
|
|
|
|
FTP Acquire Method - This is the FTP acquire method for APT.
|
|
|
|
This is a very simple implementation that does not try to optimize
|
|
at all. Commands are sent synchronously with the FTP server (as the
|
|
rfc recommends, but it is not really necessary..) and no tricks are
|
|
done to speed things along.
|
|
|
|
RFC 2428 describes the IPv6 FTP behavior
|
|
|
|
##################################################################### */
|
|
/*}}}*/
|
|
// Include Files /*{{{*/
|
|
#include <config.h>
|
|
|
|
#include <apt-pkg/configuration.h>
|
|
#include <apt-pkg/error.h>
|
|
#include <apt-pkg/fileutl.h>
|
|
#include <apt-pkg/hashes.h>
|
|
#include <apt-pkg/strutl.h>
|
|
|
|
#include <iostream>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
// Internet stuff
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "connect.h"
|
|
#include "ftp.h"
|
|
#include "rfc2553emu.h"
|
|
|
|
#include <apti18n.h>
|
|
/*}}}*/
|
|
|
|
using namespace std;
|
|
|
|
/* This table is for the EPRT and EPSV commands, it maps the OS address
|
|
family to the IETF address families */
|
|
struct AFMap
|
|
{
|
|
unsigned long Family;
|
|
unsigned long IETFFamily;
|
|
};
|
|
|
|
#ifndef AF_INET6
|
|
struct AFMap AFMap[] = {{AF_INET,1},{0, 0}};
|
|
#else
|
|
struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{0, 0}};
|
|
#endif
|
|
|
|
unsigned long TimeOut = 30;
|
|
URI Proxy;
|
|
string FtpMethod::FailFile;
|
|
int FtpMethod::FailFd = -1;
|
|
time_t FtpMethod::FailTime = 0;
|
|
|
|
// FTPConn::FTPConn - Constructor /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(MethodFd::FromFd(-1)), DataFd(-1),
|
|
DataListenFd(-1), ServerName(Srv),
|
|
ForceExtended(false), TryPassive(true),
|
|
PeerAddrLen(0), ServerAddrLen(0)
|
|
{
|
|
Debug = _config->FindB("Debug::Acquire::Ftp",false);
|
|
PasvAddr = 0;
|
|
Buffer[0] = '\0';
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::~FTPConn - Destructor /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
FTPConn::~FTPConn()
|
|
{
|
|
Close();
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Close - Close down the connection /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Just tear down the socket and data socket */
|
|
void FTPConn::Close()
|
|
{
|
|
ServerFd->Close();
|
|
close(DataFd);
|
|
DataFd = -1;
|
|
close(DataListenFd);
|
|
DataListenFd = -1;
|
|
|
|
if (PasvAddr != 0)
|
|
freeaddrinfo(PasvAddr);
|
|
PasvAddr = 0;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Open - Open a new connection /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Connect to the server using a non-blocking connection and perform a
|
|
login. */
|
|
ResultState FTPConn::Open(aptMethod *Owner)
|
|
{
|
|
// Use the already open connection if possible.
|
|
if (ServerFd->Fd() != -1)
|
|
return ResultState::SUCCESSFUL;
|
|
|
|
Close();
|
|
|
|
// Determine the proxy setting
|
|
string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
|
|
if (!SpecificProxy.empty())
|
|
{
|
|
if (SpecificProxy == "DIRECT")
|
|
Proxy = "";
|
|
else
|
|
Proxy = SpecificProxy;
|
|
}
|
|
else
|
|
{
|
|
string DefProxy = _config->Find("Acquire::ftp::Proxy");
|
|
if (!DefProxy.empty())
|
|
{
|
|
Proxy = DefProxy;
|
|
}
|
|
else
|
|
{
|
|
char* result = getenv("ftp_proxy");
|
|
Proxy = result ? result : "";
|
|
}
|
|
}
|
|
|
|
// Parse no_proxy, a , separated list of domains
|
|
if (getenv("no_proxy") != 0)
|
|
{
|
|
if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
|
|
Proxy = "";
|
|
}
|
|
|
|
// Determine what host and port to use based on the proxy settings
|
|
int Port = 0;
|
|
string Host;
|
|
if (Proxy.empty() == true)
|
|
{
|
|
if (ServerName.Port != 0)
|
|
Port = ServerName.Port;
|
|
Host = ServerName.Host;
|
|
}
|
|
else
|
|
{
|
|
if (Proxy.Port != 0)
|
|
Port = Proxy.Port;
|
|
Host = Proxy.Host;
|
|
}
|
|
|
|
/* Connect to the remote server. Since FTP is connection oriented we
|
|
want to make sure we get a new server every time we reconnect */
|
|
RotateDNS();
|
|
auto result = Connect(Host, Port, "ftp", 21, ServerFd, TimeOut, Owner);
|
|
if (result != ResultState::SUCCESSFUL)
|
|
return result;
|
|
|
|
// Login must be before getpeername otherwise dante won't work.
|
|
Owner->Status(_("Logging in"));
|
|
result = Login();
|
|
if (result != ResultState::SUCCESSFUL)
|
|
return result;
|
|
|
|
// Get the remote server's address
|
|
PeerAddrLen = sizeof(PeerAddr);
|
|
if (getpeername(ServerFd->Fd(), (sockaddr *)&PeerAddr, &PeerAddrLen) != 0)
|
|
{
|
|
_error->Errno("getpeername", _("Unable to determine the peer name"));
|
|
return ResultState::TRANSIENT_ERROR;
|
|
}
|
|
|
|
// Get the local machine's address
|
|
ServerAddrLen = sizeof(ServerAddr);
|
|
if (getsockname(ServerFd->Fd(), (sockaddr *)&ServerAddr, &ServerAddrLen) != 0)
|
|
{
|
|
_error->Errno("getsockname", _("Unable to determine the local name"));
|
|
return ResultState::TRANSIENT_ERROR;
|
|
}
|
|
|
|
return ResultState::SUCCESSFUL;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Login - Login to the remote server /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This performs both normal login and proxy login using a simples script
|
|
stored in the config file. */
|
|
ResultState FTPConn::Login()
|
|
{
|
|
unsigned int Tag;
|
|
string Msg;
|
|
|
|
// Setup the variables needed for authentication
|
|
string User = "anonymous";
|
|
string Pass = "apt_get_ftp_2.1@debian.linux.user";
|
|
|
|
// Fill in the user/pass
|
|
if (ServerName.User.empty() == false)
|
|
User = ServerName.User;
|
|
if (ServerName.Password.empty() == false)
|
|
Pass = ServerName.Password;
|
|
|
|
// Perform simple login
|
|
if (Proxy.empty() == true)
|
|
{
|
|
// Read the initial response
|
|
if (ReadResp(Tag,Msg) == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("The server refused the connection and said: %s"), Msg.c_str());
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
|
|
// Send the user
|
|
if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("USER failed, server said: %s"), Msg.c_str());
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
|
|
if (Tag == 331) { // 331 User name okay, need password.
|
|
// Send the Password
|
|
if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("PASS failed, server said: %s"), Msg.c_str());
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
}
|
|
|
|
// Enter passive mode
|
|
if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
|
|
TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
|
|
else
|
|
TryPassive = _config->FindB("Acquire::FTP::Passive",true);
|
|
}
|
|
else
|
|
{
|
|
// Read the initial response
|
|
if (ReadResp(Tag,Msg) == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("The server refused the connection and said: %s"), Msg.c_str());
|
|
return ResultState::TRANSIENT_ERROR;
|
|
}
|
|
|
|
// Perform proxy script execution
|
|
Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
|
|
if (Opts == 0 || Opts->Child == 0)
|
|
{
|
|
_error->Error(_("A proxy server was specified but no login "
|
|
"script, Acquire::ftp::ProxyLogin is empty."));
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
Opts = Opts->Child;
|
|
|
|
// Iterate over the entire login script
|
|
for (; Opts != 0; Opts = Opts->Next)
|
|
{
|
|
if (Opts->Value.empty() == true)
|
|
continue;
|
|
|
|
// Substitute the variables into the command
|
|
string Tmp = Opts->Value;
|
|
Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
|
|
Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
|
|
Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
|
|
Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
|
|
if (ServerName.Port != 0)
|
|
{
|
|
std::string SitePort;
|
|
strprintf(SitePort, "%u", ServerName.Port);
|
|
Tmp = SubstVar(Tmp,"$(SITE_PORT)", SitePort);
|
|
}
|
|
else
|
|
Tmp = SubstVar(Tmp,"$(SITE_PORT)", "21");
|
|
Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
|
|
|
|
// Send the command
|
|
if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("Login script command '%s' failed, server said: %s"), Tmp.c_str(), Msg.c_str());
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
}
|
|
|
|
// Enter passive mode
|
|
TryPassive = false;
|
|
if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
|
|
TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
|
|
else
|
|
{
|
|
if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
|
|
TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
|
|
else
|
|
TryPassive = _config->FindB("Acquire::FTP::Passive",true);
|
|
}
|
|
}
|
|
|
|
// Force the use of extended commands
|
|
if (_config->Exists("Acquire::FTP::ForceExtended::" + ServerName.Host) == true)
|
|
ForceExtended = _config->FindB("Acquire::FTP::ForceExtended::" + ServerName.Host,true);
|
|
else
|
|
ForceExtended = _config->FindB("Acquire::FTP::ForceExtended",false);
|
|
|
|
// Binary mode
|
|
if (WriteMsg(Tag,Msg,"TYPE I") == false)
|
|
return ResultState::TRANSIENT_ERROR;
|
|
if (Tag >= 400)
|
|
{
|
|
_error->Error(_("TYPE failed, server said: %s"), Msg.c_str());
|
|
return ResultState::FATAL_ERROR;
|
|
}
|
|
return ResultState::SUCCESSFUL;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::ReadLine - Read a line from the server /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This performs a very simple buffered read. */
|
|
bool FTPConn::ReadLine(string &Text)
|
|
{
|
|
if (ServerFd->Fd() == -1)
|
|
return false;
|
|
|
|
// Suck in a line
|
|
while (Len < sizeof(Buffer))
|
|
{
|
|
// Scan the buffer for a new line
|
|
for (unsigned int I = 0; I != Len; I++)
|
|
{
|
|
// Escape some special chars
|
|
if (Buffer[I] == 0)
|
|
Buffer[I] = '?';
|
|
|
|
// End of line?
|
|
if (Buffer[I] != '\n')
|
|
continue;
|
|
|
|
I++;
|
|
Text = string(Buffer,I);
|
|
memmove(Buffer,Buffer+I,Len - I);
|
|
Len -= I;
|
|
return true;
|
|
}
|
|
|
|
// Wait for some data..
|
|
if (WaitFd(ServerFd->Fd(), false, TimeOut) == false)
|
|
{
|
|
Close();
|
|
return _error->Error(_("Connection timeout"));
|
|
}
|
|
|
|
// Suck it back
|
|
int Res = ServerFd->Read(Buffer + Len, sizeof(Buffer) - Len);
|
|
if (Res == 0)
|
|
_error->Error(_("Server closed the connection"));
|
|
if (Res <= 0)
|
|
{
|
|
_error->Errno("read",_("Read error"));
|
|
Close();
|
|
return false;
|
|
}
|
|
Len += Res;
|
|
}
|
|
|
|
return _error->Error(_("A response overflowed the buffer."));
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::ReadResp - Read a full response from the server /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This reads a reply code from the server, it handles both p */
|
|
bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
|
|
{
|
|
// Grab the first line of the response
|
|
string Msg;
|
|
if (ReadLine(Msg) == false)
|
|
return false;
|
|
|
|
// Get the ID code
|
|
char *End;
|
|
Ret = strtol(Msg.c_str(),&End,10);
|
|
if (End - Msg.c_str() != 3)
|
|
return _error->Error(_("Protocol corruption"));
|
|
|
|
// All done ?
|
|
Text = Msg.c_str()+4;
|
|
if (*End == ' ')
|
|
{
|
|
if (Debug == true)
|
|
cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
|
|
return true;
|
|
}
|
|
|
|
if (*End != '-')
|
|
return _error->Error(_("Protocol corruption"));
|
|
|
|
/* Okay, here we do the continued message trick. This is foolish, but
|
|
proftpd follows the protocol as specified and wu-ftpd doesn't, so
|
|
we filter. I wonder how many clients break if you use proftpd and
|
|
put a '- in the 3rd spot in the message? */
|
|
char Leader[4];
|
|
strncpy(Leader,Msg.c_str(),3);
|
|
Leader[3] = 0;
|
|
while (ReadLine(Msg) == true)
|
|
{
|
|
// Short, it must be using RFC continuation..
|
|
if (Msg.length() < 4)
|
|
{
|
|
Text += Msg;
|
|
continue;
|
|
}
|
|
|
|
// Oops, finished
|
|
if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
|
|
{
|
|
Text += Msg.c_str()+4;
|
|
break;
|
|
}
|
|
|
|
// This message has the wu-ftpd style reply code prefixed
|
|
if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
|
|
{
|
|
Text += Msg.c_str()+4;
|
|
continue;
|
|
}
|
|
|
|
// Must be RFC style prefixing
|
|
Text += Msg;
|
|
}
|
|
|
|
if (Debug == true && _error->PendingError() == false)
|
|
cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
|
|
|
|
return !_error->PendingError();
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::WriteMsg - Send a message to the server /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Simple printf like function.. */
|
|
bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
|
|
{
|
|
va_list args;
|
|
va_start(args,Fmt);
|
|
|
|
// sprintf the description
|
|
char S[400];
|
|
vsnprintf(S,sizeof(S) - 4,Fmt,args);
|
|
strcat(S,"\r\n");
|
|
va_end(args);
|
|
|
|
if (Debug == true)
|
|
cerr << "-> '" << QuoteString(S,"") << "'" << endl;
|
|
|
|
// Send it off
|
|
unsigned long Len = strlen(S);
|
|
unsigned long Start = 0;
|
|
while (Len != 0)
|
|
{
|
|
if (WaitFd(ServerFd->Fd(), true, TimeOut) == false)
|
|
{
|
|
Close();
|
|
return _error->Error(_("Connection timeout"));
|
|
}
|
|
|
|
int Res = ServerFd->Write(S + Start, Len);
|
|
if (Res <= 0)
|
|
{
|
|
_error->Errno("write",_("Write error"));
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
Len -= Res;
|
|
Start += Res;
|
|
}
|
|
|
|
return ReadResp(Ret,Text);
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::GoPasv - Enter Passive mode /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Try to enter passive mode, the return code does not indicate if passive
|
|
mode could or could not be established, only if there was a fatal error.
|
|
We have to enter passive mode every time we make a data connection :| */
|
|
bool FTPConn::GoPasv()
|
|
{
|
|
/* The PASV command only works on IPv4 sockets, even though it could
|
|
in theory suppory IPv6 via an all zeros reply */
|
|
if (((struct sockaddr *)&PeerAddr)->sa_family != AF_INET ||
|
|
ForceExtended == true)
|
|
return ExtGoPasv();
|
|
|
|
if (PasvAddr != 0)
|
|
freeaddrinfo(PasvAddr);
|
|
PasvAddr = 0;
|
|
|
|
// Try to enable pasv mode
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (WriteMsg(Tag,Msg,"PASV") == false)
|
|
return false;
|
|
|
|
// Unsupported function
|
|
string::size_type Pos = Msg.find('(');
|
|
if (Tag >= 400)
|
|
return true;
|
|
|
|
//wu-2.6.2(1) ftp server, returns
|
|
//227 Entering Passive Mode 193,219,28,140,150,111
|
|
//without parentheses, let's try to cope with it.
|
|
//wget(1) and ftp(1) can.
|
|
if (Pos == string::npos)
|
|
Pos = Msg.rfind(' ');
|
|
else
|
|
++Pos;
|
|
|
|
// Still unsupported function
|
|
if (Pos == string::npos)
|
|
return true;
|
|
|
|
// Scan it
|
|
unsigned a0,a1,a2,a3,p0,p1;
|
|
if (sscanf(Msg.c_str() + Pos,"%u,%u,%u,%u,%u,%u",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
|
|
return true;
|
|
|
|
/* Some evil servers return 0 to mean their addr. We can actually speak
|
|
to these servers natively using IPv6 */
|
|
if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
|
|
{
|
|
// Get the IP in text form
|
|
char Name[NI_MAXHOST];
|
|
char Service[NI_MAXSERV];
|
|
getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
|
|
Name,sizeof(Name),Service,sizeof(Service),
|
|
NI_NUMERICHOST|NI_NUMERICSERV);
|
|
|
|
struct addrinfo Hints;
|
|
memset(&Hints,0,sizeof(Hints));
|
|
Hints.ai_socktype = SOCK_STREAM;
|
|
Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
|
|
Hints.ai_flags |= AI_NUMERICHOST;
|
|
|
|
// Get a new passive address.
|
|
char Port[100];
|
|
snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
|
|
if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
|
|
return true;
|
|
return true;
|
|
}
|
|
|
|
struct addrinfo Hints;
|
|
memset(&Hints,0,sizeof(Hints));
|
|
Hints.ai_socktype = SOCK_STREAM;
|
|
Hints.ai_family = AF_INET;
|
|
Hints.ai_flags |= AI_NUMERICHOST;
|
|
|
|
// Get a new passive address.
|
|
char Port[100];
|
|
snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
|
|
char Name[100];
|
|
snprintf(Name,sizeof(Name),"%u.%u.%u.%u",a0,a1,a2,a3);
|
|
if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
|
|
return true;
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::ExtGoPasv - Enter Extended Passive mode /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Try to enter extended passive mode. See GoPasv above and RFC 2428 */
|
|
bool FTPConn::ExtGoPasv()
|
|
{
|
|
if (PasvAddr != 0)
|
|
freeaddrinfo(PasvAddr);
|
|
PasvAddr = 0;
|
|
|
|
// Try to enable pasv mode
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (WriteMsg(Tag,Msg,"EPSV") == false)
|
|
return false;
|
|
|
|
// Unsupported function
|
|
string::size_type Pos = Msg.find('(');
|
|
if (Tag >= 400 || Pos == string::npos)
|
|
return true;
|
|
|
|
// Scan it
|
|
string::const_iterator List[4];
|
|
unsigned Count = 0;
|
|
Pos++;
|
|
for (string::const_iterator I = Msg.begin() + Pos; I < Msg.end(); ++I)
|
|
{
|
|
if (*I != Msg[Pos])
|
|
continue;
|
|
if (Count >= 4)
|
|
return true;
|
|
List[Count++] = I;
|
|
}
|
|
if (Count != 4)
|
|
return true;
|
|
|
|
// Break it up ..
|
|
unsigned long Proto = 0;
|
|
unsigned long Port = 0;
|
|
string IP;
|
|
IP = string(List[1]+1,List[2]);
|
|
Port = atoi(string(List[2]+1,List[3]).c_str());
|
|
if (IP.empty() == false)
|
|
Proto = atoi(string(List[0]+1,List[1]).c_str());
|
|
|
|
if (Port == 0)
|
|
return false;
|
|
|
|
// String version of the port
|
|
char PStr[100];
|
|
snprintf(PStr,sizeof(PStr),"%lu",Port);
|
|
|
|
// Get the IP in text form
|
|
struct addrinfo Hints;
|
|
memset(&Hints,0,sizeof(Hints));
|
|
Hints.ai_socktype = SOCK_STREAM;
|
|
Hints.ai_flags |= AI_NUMERICHOST;
|
|
|
|
/* The RFC defined case, connect to the old IP/protocol using the
|
|
new port. */
|
|
if (IP.empty() == true)
|
|
{
|
|
// Get the IP in text form
|
|
char Name[NI_MAXHOST];
|
|
char Service[NI_MAXSERV];
|
|
getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
|
|
Name,sizeof(Name),Service,sizeof(Service),
|
|
NI_NUMERICHOST|NI_NUMERICSERV);
|
|
IP = Name;
|
|
Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
|
|
}
|
|
else
|
|
{
|
|
// Get the family..
|
|
Hints.ai_family = 0;
|
|
for (unsigned J = 0; AFMap[J].Family != 0; J++)
|
|
if (AFMap[J].IETFFamily == Proto)
|
|
Hints.ai_family = AFMap[J].Family;
|
|
if (Hints.ai_family == 0)
|
|
return true;
|
|
}
|
|
|
|
// Get a new passive address.
|
|
if (getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr) != 0)
|
|
return true;
|
|
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Size - Return the size of a file /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Grab the file size from the server, 0 means no size or empty file */
|
|
bool FTPConn::Size(const char *Path,unsigned long long &Size)
|
|
{
|
|
// Query the size
|
|
unsigned int Tag;
|
|
string Msg;
|
|
Size = 0;
|
|
if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
|
|
return false;
|
|
|
|
char *End;
|
|
Size = strtoull(Msg.c_str(),&End,10);
|
|
if (Tag >= 400 || End == Msg.c_str())
|
|
Size = 0;
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::ModTime - Return the modification time of the file /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Like Size no error is returned if the command is not supported. If the
|
|
command fails then time is set to the current time of day to fool
|
|
date checks. */
|
|
bool FTPConn::ModTime(const char *Path, time_t &Time)
|
|
{
|
|
Time = time(&Time);
|
|
|
|
// Query the mod time
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
|
|
return false;
|
|
if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
|
|
return true;
|
|
|
|
// Parse it
|
|
return FTPMDTMStrToTime(Msg.c_str(), Time);
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::CreateDataFd - Get a data connection /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Create the data connection. Call FinalizeDataFd after this though.. */
|
|
bool FTPConn::CreateDataFd()
|
|
{
|
|
close(DataFd);
|
|
DataFd = -1;
|
|
|
|
// Attempt to enter passive mode.
|
|
if (TryPassive == true)
|
|
{
|
|
if (GoPasv() == false)
|
|
return false;
|
|
|
|
// Oops, didn't work out, don't bother trying again.
|
|
if (PasvAddr == 0)
|
|
TryPassive = false;
|
|
}
|
|
|
|
// Passive mode?
|
|
if (PasvAddr != 0)
|
|
{
|
|
// Get a socket
|
|
if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
|
|
PasvAddr->ai_protocol)) < 0)
|
|
return _error->Errno("socket",_("Could not create a socket"));
|
|
|
|
// Connect to the server
|
|
SetNonBlock(DataFd,true);
|
|
if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
|
|
errno != EINPROGRESS)
|
|
return _error->Errno("socket",_("Could not create a socket"));
|
|
|
|
/* This implements a timeout for connect by opening the connection
|
|
nonblocking */
|
|
if (WaitFd(DataFd,true,TimeOut) == false)
|
|
return _error->Error(_("Could not connect data socket, connection timed out"));
|
|
unsigned int Err;
|
|
unsigned int Len = sizeof(Err);
|
|
if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
|
|
return _error->Errno("getsockopt",_("Failed"));
|
|
if (Err != 0)
|
|
return _error->Error(_("Could not connect passive socket."));
|
|
|
|
return true;
|
|
}
|
|
|
|
// Port mode :<
|
|
close(DataListenFd);
|
|
DataListenFd = -1;
|
|
|
|
// Get the information for a listening socket.
|
|
struct addrinfo *BindAddr = NULL;
|
|
struct addrinfo Hints;
|
|
memset(&Hints,0,sizeof(Hints));
|
|
Hints.ai_socktype = SOCK_STREAM;
|
|
Hints.ai_flags |= AI_PASSIVE;
|
|
Hints.ai_family = ((struct sockaddr *)&ServerAddr)->sa_family;
|
|
if (getaddrinfo(0,"0",&Hints,&BindAddr) != 0 || BindAddr == NULL)
|
|
return _error->Error(_("getaddrinfo was unable to get a listening socket"));
|
|
|
|
// Construct the socket
|
|
if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
|
|
BindAddr->ai_protocol)) < 0)
|
|
{
|
|
freeaddrinfo(BindAddr);
|
|
return _error->Errno("socket",_("Could not create a socket"));
|
|
}
|
|
|
|
// Bind and listen
|
|
if (::bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
|
|
{
|
|
freeaddrinfo(BindAddr);
|
|
return _error->Errno("bind",_("Could not bind a socket"));
|
|
}
|
|
freeaddrinfo(BindAddr);
|
|
if (listen(DataListenFd,1) < 0)
|
|
return _error->Errno("listen",_("Could not listen on the socket"));
|
|
SetNonBlock(DataListenFd,true);
|
|
|
|
// Determine the name to send to the remote
|
|
struct sockaddr_storage Addr;
|
|
socklen_t AddrLen = sizeof(Addr);
|
|
if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
|
|
return _error->Errno("getsockname",_("Could not determine the socket's name"));
|
|
|
|
|
|
// Reverse the address. We need the server address and the data port.
|
|
char Name[NI_MAXHOST];
|
|
char Service[NI_MAXSERV];
|
|
char Service2[NI_MAXSERV];
|
|
getnameinfo((struct sockaddr *)&Addr,AddrLen,
|
|
Name,sizeof(Name),Service,sizeof(Service),
|
|
NI_NUMERICHOST|NI_NUMERICSERV);
|
|
getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
|
|
Name,sizeof(Name),Service2,sizeof(Service2),
|
|
NI_NUMERICHOST|NI_NUMERICSERV);
|
|
|
|
// Send off an IPv4 address in the old port format
|
|
if (((struct sockaddr *)&Addr)->sa_family == AF_INET &&
|
|
ForceExtended == false)
|
|
{
|
|
// Convert the dots in the quad into commas
|
|
for (char *I = Name; *I != 0; I++)
|
|
if (*I == '.')
|
|
*I = ',';
|
|
unsigned long Port = atoi(Service);
|
|
|
|
// Send the port command
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
|
|
Name,
|
|
(int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
|
|
return false;
|
|
if (Tag >= 400)
|
|
return _error->Error(_("Unable to send PORT command"));
|
|
return true;
|
|
}
|
|
|
|
// Construct an EPRT command
|
|
unsigned Proto = 0;
|
|
for (unsigned J = 0; AFMap[J].Family != 0; J++)
|
|
if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
|
|
Proto = AFMap[J].IETFFamily;
|
|
if (Proto == 0)
|
|
return _error->Error(_("Unknown address family %u (AF_*)"),
|
|
((struct sockaddr *)&Addr)->sa_family);
|
|
|
|
// Send the EPRT command
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
|
|
return false;
|
|
if (Tag >= 400)
|
|
return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Finalize - Complete the Data connection /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* If the connection is in port mode this waits for the other end to hook
|
|
up to us. */
|
|
bool FTPConn::Finalize()
|
|
{
|
|
// Passive mode? Do nothing
|
|
if (PasvAddr != 0)
|
|
return true;
|
|
|
|
// Close any old socket..
|
|
close(DataFd);
|
|
DataFd = -1;
|
|
|
|
// Wait for someone to connect..
|
|
if (WaitFd(DataListenFd,false,TimeOut) == false)
|
|
return _error->Error(_("Data socket connect timed out"));
|
|
|
|
// Accept the connection
|
|
struct sockaddr_in Addr;
|
|
socklen_t Len = sizeof(Addr);
|
|
DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
|
|
if (DataFd < 0)
|
|
return _error->Errno("accept",_("Unable to accept connection"));
|
|
|
|
close(DataListenFd);
|
|
DataListenFd = -1;
|
|
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FTPConn::Get - Get a file /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This opens a data connection, sends REST and RETR and then
|
|
transfers the file over. */
|
|
bool FTPConn::Get(const char *Path,FileFd &To,unsigned long long Resume,
|
|
Hashes &Hash,bool &Missing, unsigned long long MaximumSize,
|
|
pkgAcqMethod *Owner)
|
|
{
|
|
Missing = false;
|
|
if (CreateDataFd() == false)
|
|
return false;
|
|
|
|
unsigned int Tag;
|
|
string Msg;
|
|
if (Resume != 0)
|
|
{
|
|
if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
|
|
return false;
|
|
if (Tag >= 400)
|
|
Resume = 0;
|
|
}
|
|
|
|
if (To.Truncate(Resume) == false)
|
|
return false;
|
|
|
|
if (To.Seek(0) == false)
|
|
return false;
|
|
|
|
if (Resume != 0)
|
|
{
|
|
if (Hash.AddFD(To,Resume) == false)
|
|
{
|
|
_error->Errno("read",_("Problem hashing file"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Send the get command
|
|
if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
|
|
return false;
|
|
|
|
if (Tag >= 400)
|
|
{
|
|
if (Tag == 550)
|
|
Missing = true;
|
|
return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
|
|
}
|
|
|
|
// Finish off the data connection
|
|
if (Finalize() == false)
|
|
return false;
|
|
|
|
// Copy loop
|
|
unsigned char Buffer[4096];
|
|
while (1)
|
|
{
|
|
// Wait for some data..
|
|
if (WaitFd(DataFd,false,TimeOut) == false)
|
|
{
|
|
Close();
|
|
return _error->Error(_("Data socket timed out"));
|
|
}
|
|
|
|
// Read the data..
|
|
int Res = read(DataFd,Buffer,sizeof(Buffer));
|
|
if (Res == 0)
|
|
break;
|
|
if (Res < 0)
|
|
{
|
|
if (errno == EAGAIN)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
Hash.Add(Buffer,Res);
|
|
if (To.Write(Buffer,Res) == false)
|
|
{
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
if (MaximumSize > 0 && To.Tell() > MaximumSize)
|
|
{
|
|
Owner->SetFailReason("MaximumSizeExceeded");
|
|
return _error->Error(_("File has unexpected size (%llu != %llu). Mirror sync in progress?"),
|
|
To.Tell(), MaximumSize);
|
|
}
|
|
}
|
|
|
|
// All done
|
|
close(DataFd);
|
|
DataFd = -1;
|
|
|
|
// Read the closing message from the server
|
|
if (ReadResp(Tag,Msg) == false)
|
|
return false;
|
|
if (Tag >= 400)
|
|
return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
|
|
// FtpMethod::FtpMethod - Constructor /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
FtpMethod::FtpMethod() : aptAuthConfMethod("ftp", "1.0", SendConfig | SendURIEncoded)
|
|
{
|
|
SeccompFlags = aptMethod::BASE | aptMethod::NETWORK;
|
|
signal(SIGTERM,SigTerm);
|
|
signal(SIGINT,SigTerm);
|
|
|
|
Server = 0;
|
|
FailFd = -1;
|
|
}
|
|
/*}}}*/
|
|
// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This closes and timestamps the open file. This is necessary to get
|
|
resume behavior on user abort */
|
|
void FtpMethod::SigTerm(int)
|
|
{
|
|
if (FailFd == -1)
|
|
_exit(100);
|
|
|
|
// Timestamp
|
|
struct timeval times[2];
|
|
times[0].tv_sec = FailTime;
|
|
times[1].tv_sec = FailTime;
|
|
times[0].tv_usec = times[1].tv_usec = 0;
|
|
utimes(FailFile.c_str(), times);
|
|
|
|
close(FailFd);
|
|
|
|
_exit(100);
|
|
}
|
|
/*}}}*/
|
|
// FtpMethod::Configuration - Handle a configuration message /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* We stash the desired pipeline depth */
|
|
bool FtpMethod::Configuration(string Message)
|
|
{
|
|
if (aptAuthConfMethod::Configuration(Message) == false)
|
|
return false;
|
|
|
|
TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
|
|
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// FtpMethod::Fetch - Fetch a file /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* Fetch a single file, called by the base class.. */
|
|
bool FtpMethod::Fetch(FetchItem *Itm)
|
|
{
|
|
URI Get(Itm->Uri);
|
|
auto const File = DecodeSendURI(Get.Path);
|
|
FetchResult Res;
|
|
Res.Filename = Itm->DestFile;
|
|
Res.IMSHit = false;
|
|
|
|
MaybeAddAuthTo(Get);
|
|
|
|
// Connect to the server
|
|
if (Server == 0 || Server->Comp(Get) == false)
|
|
{
|
|
delete Server;
|
|
Server = new FTPConn(Get);
|
|
}
|
|
|
|
// Could not connect is a transient error..
|
|
switch (Server->Open(this))
|
|
{
|
|
case ResultState::TRANSIENT_ERROR:
|
|
Server->Close();
|
|
Fail(true);
|
|
return true;
|
|
case ResultState::FATAL_ERROR:
|
|
Server->Close();
|
|
Fail(false);
|
|
return true;
|
|
case ResultState::SUCCESSFUL:
|
|
break;
|
|
}
|
|
|
|
// Get the files information
|
|
Status(_("Query"));
|
|
unsigned long long Size;
|
|
if (not Server->Size(File.c_str(), Size) ||
|
|
not Server->ModTime(File.c_str(), FailTime))
|
|
{
|
|
Fail(true);
|
|
return true;
|
|
}
|
|
Res.Size = Size;
|
|
|
|
// See if it is an IMS hit
|
|
if (Itm->LastModified == FailTime)
|
|
{
|
|
Res.Size = 0;
|
|
Res.IMSHit = true;
|
|
URIDone(Res);
|
|
return true;
|
|
}
|
|
|
|
// See if the file exists
|
|
struct stat Buf;
|
|
if (stat(Itm->DestFile.c_str(),&Buf) == 0)
|
|
{
|
|
if (Size == (unsigned long long)Buf.st_size && FailTime == Buf.st_mtime)
|
|
{
|
|
Res.Size = Buf.st_size;
|
|
Res.LastModified = Buf.st_mtime;
|
|
Res.ResumePoint = Buf.st_size;
|
|
URIDone(Res);
|
|
return true;
|
|
}
|
|
|
|
// Resume?
|
|
if (FailTime == Buf.st_mtime && Size > (unsigned long long)Buf.st_size)
|
|
Res.ResumePoint = Buf.st_size;
|
|
}
|
|
|
|
// Open the file
|
|
Hashes Hash(Itm->ExpectedHashes);
|
|
{
|
|
FileFd Fd(Itm->DestFile,FileFd::WriteAny);
|
|
if (_error->PendingError() == true)
|
|
return false;
|
|
|
|
URIStart(Res);
|
|
|
|
FailFile = Itm->DestFile;
|
|
FailFile.c_str(); // Make sure we don't do a malloc in the signal handler
|
|
FailFd = Fd.Fd();
|
|
|
|
bool Missing;
|
|
if (not Server->Get(File.c_str(), Fd, Res.ResumePoint, Hash, Missing, Itm->MaximumSize, this))
|
|
{
|
|
Fd.Close();
|
|
|
|
// Timestamp
|
|
struct timeval times[2];
|
|
times[0].tv_sec = FailTime;
|
|
times[1].tv_sec = FailTime;
|
|
times[0].tv_usec = times[1].tv_usec = 0;
|
|
utimes(FailFile.c_str(), times);
|
|
|
|
// If the file is missing we hard fail and delete the destfile
|
|
// otherwise transient fail
|
|
if (Missing == true) {
|
|
RemoveFile("ftp", FailFile);
|
|
return false;
|
|
}
|
|
Fail(true);
|
|
return true;
|
|
}
|
|
|
|
Res.Size = Fd.Size();
|
|
|
|
// Timestamp
|
|
struct timeval times[2];
|
|
times[0].tv_sec = FailTime;
|
|
times[1].tv_sec = FailTime;
|
|
times[0].tv_usec = times[1].tv_usec = 0;
|
|
utimes(Fd.Name().c_str(), times);
|
|
FailFd = -1;
|
|
}
|
|
|
|
Res.LastModified = FailTime;
|
|
Res.TakeHashes(Hash);
|
|
|
|
URIDone(Res);
|
|
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
|
|
int main(int, const char *argv[])
|
|
{
|
|
/* See if we should be come the http client - we do this for http
|
|
proxy urls */
|
|
if (getenv("ftp_proxy") != 0)
|
|
{
|
|
URI Proxy(string(getenv("ftp_proxy")));
|
|
|
|
// Run the HTTP method
|
|
if (Proxy.Access == "http")
|
|
{
|
|
// Copy over the environment setting
|
|
char S[300];
|
|
snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
|
|
putenv(S);
|
|
putenv((char *)"no_proxy=");
|
|
|
|
// Run the http method
|
|
string Path = flNotFile(argv[0]) + "http";
|
|
execl(Path.c_str(),Path.c_str(),(char *)NULL);
|
|
cerr << _("Unable to invoke ") << Path << endl;
|
|
exit(100);
|
|
}
|
|
}
|
|
return FtpMethod().Run();
|
|
}
|