/*****************************************************************************/
/*
                                 TCPIPalt.c

Alternate TCP/IP functions, lookup by name and by address.

Currently experimental.

Both inline (synchronous) and by agent (DCL script, asynchronous).

The agent can take on the role of a network agent and drop connections.  This
means that if using a lookup agent there is no need also to use a network
agent.  A drop connection is cached.

These are (currently) controlled using logical names WASD_TCPIP_LOOKUP_INLINE
and WASD_TCPIP_LOOKUP_AGENT.  Without one or the other the (historic) TCP/IP
$QIO INETACP_FUNC$C_GETHOSTBYADDR and INETACP_FUNC$C_GETHOSTBYNAME are used.


VERSION HISTORY
---------------
01-JUN-2021  MGD  initial
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <lnmdef.h>
#include <ssdef.h>

/* application-related header files */
#include "wasd.h"
#include "tcpip.h"

#define WASD_MODULE "TCPIPALT"

#define FI_LI WASD_MODULE, __LINE__

#define DBUG 1

/******************/
/* global storage */
/******************/

char  TcpIpAltAgentScript [] = "/cgiplus-bin/lookagent";

char  ProblemTcpIpAgent [] = "Problem initiating TCP/IP DNS agent";

#define TCPIP_AGENT_ACTIVE_MAX 8
#define TCPIP_AGENT_BUSY_MAX 100
static int  TcpIpAltAgentActiveCount,
            TcpIpAltAgentActiveMax = TCPIP_AGENT_ACTIVE_MAX,
            TcpIpAltAgentBusyMax = TCPIP_AGENT_BUSY_MAX,
            TcpIpAltAgentBusyCount,
            TcpIpAltAgentBusyLimit;

/********************/
/* external storage */
/********************/

extern int  HttpdTickSecond;
            TcpIpLookupAddrCount,
            TcpIpLookupNameCount;

extern const int64  Delta100mSec;

extern uint64  TcpIpLookupAddrDelta64,
               TcpIpLookupAddrMax64,
               TcpIpLookupAddrMin64,
               TcpIpLookupNameDelta64,
               TcpIpLookupNameMax64,
               TcpIpLookupNameMin64;

extern char  ErrorSanityCheck[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Not actually used :-)
TcpIpNameToAddress() calls the relevant inline functions directly.
*/

#if 0

int TcpIpAltInlineLookup (TCPIP_HOST_LOOKUP *hlptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltInlineLookup()");

   if (hlptr->AddrToName)
      status = TcpIpAltInlineAddressToName (hlptr);
   else
      status = TcpIpAltInlineNameToAddress (hlptr);

   return (status);
}

#endif /* 0 */

/*****************************************************************************/
/*
Set up by TcpIpAddressToName().
*/

int TcpIpAltInlineAddressToName (TCPIP_HOST_LOOKUP *hlptr)

{
   int  retval;
   char  *cptr, *sptr, *zptr;
   char  strerr [128];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltInlineAddressToName()");

   retval = TcpIpAltGetNameInfo (hlptr);
   if (retval)
   {
      hlptr->LookupIOsb.Status = SS$_ENDOFFILE;
      *(USHORTPTR)hlptr->HostName = '?\0';
      hlptr->HostNameLength = 1;
   }
   else
   {
      hlptr->LookupIOsb.Status = SS$_NORMAL;
      hlptr->HostNameLength = strlen(hlptr->HostName);
   }

   TcpIpLookupDelta (hlptr);

   TcpIpCacheSetEntry (hlptr);

   InstanceGblSecIncrLong (&AccountingPtr->LookupDnsAddressCount);

   if (WATCH_MODULE(WATCH_MOD_NET))
   {
      sprintf (strerr, "%s", (char*)gai_strerror(retval));
      WatchThis (WATCHALL, WATCH_MOD_NET,
"host:!AZ retval:!UL (!AZ) !&S delta:!ULmS min:!ULmS max:!ULmS",
                 &hlptr->HostName, retval, strerr, hlptr->LookupIOsb.Status,
                 hlptr->LookupDeltaTime64 / 10000,
                 TcpIpLookupNameMin64 / 10000,
                 TcpIpLookupNameMax64 / 10000);
   }

   if (hlptr->AstFunction) SysDclAst (hlptr->AstFunction, hlptr->AstParam);

   return (hlptr->LookupIOsb.Status);
}

/*****************************************************************************/
/*
Wrap the getnameinfo() name resolution function.  SYNCHRONOUS!
*/

int TcpIpAltGetNameInfo (TCPIP_HOST_LOOKUP *hlptr)

{
   static int  flags = NI_NAMEREQD;

   int  retval;
   struct sockaddr_in  SockAddr;
   struct sockaddr_in6  Sock6Addr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "TcpIpAltGetNameInfo() !&Z !UL",
                 hlptr->HostName, IPADDRESS_SIZE(&hlptr->IpAddress));

   *(ULONGPTR)hlptr->HostName = 0;

   if (IPADDRESS_IS_V4 (&hlptr->IpAddress))
   {
      memset (&SockAddr, 0, sizeof(SockAddr));
      SockAddr.sin_family = TCPIP$C_AF_INET;
      memcpy (&SockAddr.sin_addr,
              IPADDRESS_ADR4(&hlptr->IpAddress),
              sizeof(SockAddr.sin_addr));

      retval = getnameinfo (&SockAddr, sizeof(SockAddr),
                           hlptr->HostName, sizeof(hlptr->HostName),
                           NULL, NULL, flags);
   }
   else
   if (IPADDRESS_IS_V6 (&hlptr->IpAddress))
   {
      memset (&Sock6Addr, 0, sizeof(Sock6Addr));
      Sock6Addr.sin6_family = TCPIP$C_AF_INET6;
      memcpy (&Sock6Addr.sin6_addr,
              IPADDRESS_ADR6(&hlptr->IpAddress),
              sizeof(Sock6Addr.sin6_addr));

      retval = getnameinfo (&Sock6Addr, sizeof(Sock6Addr),
                           hlptr->HostName, sizeof(hlptr->HostName),
                           NULL, NULL, flags);
   }
   else
      return (false);

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "retval:!UL host:!&Z", retval, hlptr->HostName);

   return (retval);
}

/*****************************************************************************/
/*
Set up by TcpIpNameToAddress().
*/

int TcpIpAltInlineNameToAddress (TCPIP_HOST_LOOKUP *hlptr)

{
   int  retval;
   char  *cptr, *sptr, *zptr;
   char  strerr [128];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltInlineNameToAddress()");

   if (retval = TcpIpAltGet4AddrInfo (hlptr))
      retval = TcpIpAltGet6AddrInfo (hlptr);

   if (retval)
   {
      hlptr->LookupIOsb.Status = SS$_ENDOFFILE;
      IPADDRESS_SET_UNUSABLE (&hlptr->IpAddress);
   }
   else
      hlptr->LookupIOsb.Status = SS$_NORMAL;

   TcpIpLookupDelta (hlptr);

   InstanceGblSecIncrLong (&AccountingPtr->LookupDnsNameCount);

   if (WATCH_MODULE(WATCH_MOD_NET))
   {
      sprintf (strerr, "%s", (char*)gai_strerror(retval));
      WatchThis (WATCHALL, WATCH_MOD_NET,
"IP:!&I retval:!UL (!AZ) !&S delta:!ULmS min:!ULmS max:!ULmS",
                 &hlptr->IpAddress, retval, strerr, hlptr->LookupIOsb.Status,
                 hlptr->LookupDeltaTime64 / 10000,
                 TcpIpLookupNameMin64 / 10000,
                 TcpIpLookupNameMax64 / 10000);
   }

   if (hlptr->AstFunction) SysDclAst (hlptr->AstFunction, hlptr->AstParam);

   return (hlptr->LookupIOsb.Status);
}

/*****************************************************************************/
/*
Wrap the getaddrinfo() address resolution function.  SYNCHRONOUS!
*/

int TcpIpAltGet4AddrInfo (TCPIP_HOST_LOOKUP *hlptr)

{
   static struct addrinfo  IPv4Hint = { AI_ALL, TCPIP$C_AF_INET,
                                        0, 0, 0, NULL, NULL, NULL };
   int  retval;
   void  *addptr;
   struct  addrinfo  *raiptr,
                     *RetAddrInfo;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltGet4AddrInfo() !&Z !UL",
                 hlptr->HostName, IPADDRESS_SIZE(&hlptr->IpAddress));

   IPADDRESS_V4 (&hlptr->IpAddress);

   retval = getaddrinfo (hlptr->HostName, NULL, &IPv4Hint, &RetAddrInfo);

   if (retval) return (retval);

   for (raiptr = RetAddrInfo; raiptr; raiptr = raiptr->ai_next)
   {
      if (raiptr->ai_family == AF_INET)
      {
         addptr = &((struct sockaddr_in *)raiptr->ai_addr)->sin_addr;
         IPADDRESS_GET4 (&hlptr->IpAddress, addptr);
         TcpIpCacheSetEntry (hlptr);
      }
   }

   freeaddrinfo (&RetAddrInfo);

   return (retval);
}

/*****************************************************************************/
/*
Wrap the getaddrinfo() address resolution function.  SYNCHRONOUS!
*/

int TcpIpAltGet6AddrInfo (TCPIP_HOST_LOOKUP *hlptr)

{
   static struct addrinfo  IPv6Hint = { AI_ALL, TCPIP$C_AF_INET,
                                        0, 0, 0, NULL, NULL, NULL };
   int  retval;
   void  *addptr;
   struct  addrinfo  *raiptr,
                     *RetAddrInfo;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltGet6AddrInfo() !&Z !UL",
                 hlptr->HostName, IPADDRESS_SIZE(&hlptr->IpAddress));

   IPADDRESS_V6 (&hlptr->IpAddress);

   retval = getaddrinfo (hlptr->HostName, NULL, &IPv6Hint, &RetAddrInfo);

   if (retval) return (retval);

   for (raiptr = RetAddrInfo; raiptr; raiptr = raiptr->ai_next)
   {
      if (raiptr->ai_family == AF_INET6)
      {
         addptr = &((struct sockaddr_in6 *)raiptr->ai_addr)->sin6_addr;
         IPADDRESS_GET6 (&hlptr->IpAddress, addptr);
      }
   }

   freeaddrinfo (&RetAddrInfo);

   return (retval);
}

/*****************************************************************************/
/*
Activate a TCP/IP DNS agent (script) to perform the lookup.  ASYNCHRONOUS!
A TCP/IP agent does not have an associated request.
*/

int TcpIpAltAgentBegin (TCPIP_HOST_LOOKUP *hlptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAgentBegin()");

   if (!hlptr->AstFunction)
   {
      /* no AST supplied then do it inline */
      if (hlptr->AddrToName)
         status = TcpIpAltInlineAddressToName (hlptr);
      else
         status = TcpIpAltInlineNameToAddress (hlptr);
      return (status);
   }

   if (TcpIpAltAgentActiveCount >= TcpIpAltAgentActiveMax)
   {
      /* busy, busy, busy ... requeue for a delayed subsequent attempt */
      if (hlptr->AgentBusyCount++ <= TcpIpAltAgentBusyMax)
      {
         if (hlptr->AgentBusyCount <= TcpIpAltAgentBusyMax / 4)
            SysDclAst (TcpIpAltAgentBegin, hlptr);
         else
            sys$setimr (0, &Delta100mSec, TcpIpAltAgentBegin, hlptr, 0);
         return (SS$_NORMAL);
      }
      else
      {
         ErrorNoticed (NULL, SS$_TOOMANYREDS, "!UL exceeded (!UL)",
                       FI_LI, TcpIpAltAgentBusyMax, TcpIpAltAgentBusyLimit);
         TcpIpAltAgentBusyCount++;
         hlptr->LookupIOsb.Status = SS$_ABORT;
         SysDclAst (hlptr->AstFunction, hlptr->AstParam);
         return (SS$_ABORT);
      }
   }
   if (hlptr->AgentBusyCount)
   {
      if (hlptr->AgentBusyCount > TcpIpAltAgentBusyLimit)
         TcpIpAltAgentBusyLimit = hlptr->AgentBusyCount;
      hlptr->AgentBusyCount = 0;
   }

   /************/
   /* continue */
   /************/

   rqptr = DclFauxRequest (NULL, NULL, NULL);

   rqptr->AgentAstParam = hlptr;

   rqptr->AgentRequestPtr = VmGetHeap (rqptr, 256);
   if (hlptr->AddrToName)
      FaoToBuffer (rqptr->AgentRequestPtr, 256, NULL,
                   "!&I", &hlptr->IpAddress);
   else
      FaoToBuffer (rqptr->AgentRequestPtr, 256, NULL,
                   "!AZ", hlptr->HostName);

   rqptr = DclFauxRequest (rqptr, TcpIpAltAgentScript, &TcpIpAltAgentEnd);

   if (!rqptr)
   {
      if (WATCHING (rqptr, WATCH_NETWORK))
         WatchThis (WATCHITM(rqptr), WATCH_NETWORK, "LOOKUP AGENT !AZ",
                    TcpIpAltAgentScript);
      ErrorNoticed (NULL, SS$_ABORT, ProblemTcpIpAgent, FI_LI);
      hlptr->LookupIOsb.Status = SS$_ABORT;
      SysDclAst (hlptr->AstFunction, hlptr->AstParam);
      return (SS$_ABORT);
   }

   TcpIpAltAgentActiveCount++;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The agent (script) has completed.
*/

void TcpIpAltAgentEnd (REQUEST_STRUCT *rqptr)

{
   int  count, status;
   char  *cptr, *sptr, *zptr;
   TCPIP_HOST_LOOKUP  *hlptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIpAltAgentEnd()");

   hlptr = (TCPIP_HOST_LOOKUP*)rqptr->AgentAstParam;

   TcpIpLookupDelta (hlptr);

   if (cptr = rqptr->AgentResponsePtr)
   {
      status = atoi(cptr);
      while (isdigit (*cptr)) cptr++;
      while (*cptr == ' ') cptr++;

      if (status >= 200 && status <= 299)
      {
         /***********/
         /* success */
         /***********/

         if (hlptr->AddrToName)
         {
            hlptr->LookupIOsb.Status = SS$_NORMAL;
            zptr = (sptr = hlptr->HostName) + sizeof(hlptr->HostName)-1;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';
            hlptr->HostNameLength = sptr - hlptr->HostName;
            TcpIpCacheSetEntry (hlptr);
         }
         else
         {
            /* possible multiple space-separated IP addresses */
            hlptr->LookupIOsb.Status = SS$_NORMAL;
            count = 0;
            while (*cptr)
            {
               while (*cptr && *cptr == ' ') cptr++;
               if (!*cptr) break;
               status = TcpIpStringToAddress (cptr, &hlptr->IpAddress);
               if (VMSnok (status)) 
               {
                  if (!count)
                  {
                     /* primary host address */
                     hlptr->LookupIOsb.Status = status;
                     IPADDRESS_SET_UNUSABLE (&hlptr->IpAddress);
                     ErrorNoticed (NULL, SS$_ABORT, cptr, FI_LI);
                     break;
                  }
               }
               count++;
               TcpIpCacheSetEntry (hlptr);
               while (*cptr && *cptr != ' ') cptr++;
            }
         }
      }
      else
      if (status == 418)
      {
         /********/
         /* drop */
         /********/

         hlptr->DropConnect = true;
         TcpIpCacheSetEntry (hlptr);
      }
      else
      {
         /**********/
         /* failed */
         /**********/

         if (hlptr->AddrToName)
         {
            hlptr->LookupIOsb.Status = SS$_ENDOFFILE;
            *(USHORTPTR)hlptr->HostName = '?\0';
            hlptr->HostNameLength = 1;
         }
         else
            IPADDRESS_SET_UNUSABLE (&hlptr->IpAddress);
         TcpIpCacheSetEntry (hlptr);

         if (MATCH3 (cptr, "[%X"))
         {
            /* if agent reporting VMS error */
            status = strtol (cptr+3, NULL, 16);
            ErrorNoticed (NULL, status, cptr, FI_LI);
            hlptr->LookupIOsb.Status = status;
         }
      }
   }
   else
   {
      ErrorNoticed (NULL, SS$_ABORT, ProblemTcpIpAgent, FI_LI);
      hlptr->LookupIOsb.Status = SS$_ABORT;
   }

   if (TcpIpAltAgentActiveCount) TcpIpAltAgentActiveCount--;

   SysDclAst (hlptr->AstFunction, hlptr->AstParam);

   /* remove from any supervisory list */
   HttpdSupervisorList (rqptr, -1);

   VmFreeRequest (rqptr, FI_LI);
}

/*****************************************************************************/

