// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
// $Id: connect.cc,v 1.4 1999/11/19 05:01:54 jgg Exp $
/* ######################################################################

   Connect - Replacement connect call
   
   ##################################################################### */
									/*}}}*/
// Include Files							/*{{{*/
#include "connect.h"
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>

#include <stdio.h>
#include <errno.h>
#include <unistd.h>

// Internet stuff
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "rfc2553emu.h"
									/*}}}*/

static string LastHost;
static int LastPort = 0;
static struct addrinfo *LastHostAddr = 0;
static struct addrinfo *LastUsed = 0;

// DoConnect - Attempt a connect operation				/*{{{*/
// ---------------------------------------------------------------------
/* This helper function attempts a connection to a single address. */
static bool DoConnect(struct addrinfo *Addr,string Host,
		      unsigned long TimeOut,int &Fd,pkgAcqMethod *Owner)
{
   // Show a status indicator
   char Name[NI_MAXHOST];
   Name[0] = 0;
   getnameinfo(Addr->ai_addr,Addr->ai_addrlen,
	       Name,sizeof(Name),0,0,NI_NUMERICHOST);
   Owner->Status("Connecting to %s (%s)",Host.c_str(),Name);
   
   // Get a socket
   if ((Fd = socket(Addr->ai_family,Addr->ai_socktype,
		    Addr->ai_protocol)) < 0)
      return _error->Errno("socket","Could not create a socket");
   
   SetNonBlock(Fd,true);
   if (connect(Fd,Addr->ai_addr,Addr->ai_addrlen) < 0 &&
       errno != EINPROGRESS)
      return _error->Errno("connect","Cannot initiate the connection "
			   "to %s (%s).",Host.c_str(),Name);
   
   /* This implements a timeout for connect by opening the connection
      nonblocking */
   if (WaitFd(Fd,true,TimeOut) == false)
      return _error->Error("Could not connect to %s (%s), "
			   "connection timed out",Host.c_str(),Name);
   
   // Check the socket for an error condition
   unsigned int Err;
   unsigned int Len = sizeof(Err);
   if (getsockopt(Fd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
      return _error->Errno("getsockopt","Failed");
   
   if (Err != 0)
      return _error->Error("Could not connect to %s (%s).",Host.c_str(),Name);

   return true;
}
									/*}}}*/
// Connect - Connect to a server					/*{{{*/
// ---------------------------------------------------------------------
/* Performs a connection to the server */
bool Connect(string Host,int Port,const char *Service,int DefPort,int &Fd,
	     unsigned long TimeOut,pkgAcqMethod *Owner)
{
   if (_error->PendingError() == true)
      return false;
   
   /* We used a cached address record.. Yes this is against the spec but
      the way we have setup our rotating dns suggests that this is more
      sensible */
   if (LastHost != Host || LastPort != Port)
   {
      Owner->Status("Connecting to %s",Host.c_str());

      // Lookup the host
      char S[300];
      if (Port != 0)
	 snprintf(S,sizeof(S),"%u",Port);
      else
	 snprintf(S,sizeof(S),"%s",Service);

      // Free the old address structure
      if (LastHostAddr != 0)
      {
	 freeaddrinfo(LastHostAddr);
	 LastHostAddr = 0;
      }
      
      // We only understand SOCK_STREAM sockets.
      struct addrinfo Hints;
      memset(&Hints,0,sizeof(Hints));
      Hints.ai_socktype = SOCK_STREAM;
      Hints.ai_protocol = IPPROTO_TCP;       // Right?
      
      // Resolve both the host and service simultaneously
      while (1)
      {
	 int Res;
	 if ((Res = getaddrinfo(Host.c_str(),S,&Hints,&LastHostAddr)) != 0 ||
	     LastHostAddr == 0)
	 {
	    if (Res == EAI_NONAME || Res == EAI_SERVICE)
	    {
	       if (DefPort != 0)
	       {
		  snprintf(S,sizeof(S),"%u",DefPort);
		  DefPort = 0;
		  continue;
	       }
	       return _error->Error("Could not resolve '%s'",Host.c_str());
	    }
	    
	    return _error->Error("Something wicked happend resolving '%s/%s'",
				 Host.c_str(),S);
	 }
	 break;
      }
      
      if (LastHostAddr->ai_family == AF_UNIX)
	 return _error->Error("getaddrinfo returned a unix domain socket\n");
      
      LastHost = Host;
      LastPort = Port;
      LastUsed = 0;
   }

   // Get the printable IP address
   struct addrinfo *CurHost = LastHostAddr;
   if (LastUsed != 0)
       CurHost = LastUsed;
   
   while (CurHost != 0)
   {
      if (DoConnect(CurHost,Host,TimeOut,Fd,Owner) == true)
      {
	 LastUsed = CurHost;
	 return true;
      }      
      close(Fd);
      Fd = -1;
      
      CurHost = CurHost->ai_next;
      LastUsed = 0;
      if (CurHost != 0)
	 _error->Discard();
   }
   
   return false;
}
									/*}}}*/