/*****************************************************************************/
/*
                                  TCPIP6.c

IPv6 address resolution functions only available non-VAX VMS V7.0 and later.

If the logical name WASD_TCPIP6_IPNODES is not defined none of this happens.

IPv6 name/address resolution is a little problematic for WASD because there is
(currently) no native asynchronous interface to it in the same way as there is
with $QIO ACPCONTROL for IPv4.  Fortunately IPv6 is somewhat of a niche
environment!

The function TcpIp6Nodes() allows a WASD-specific logical name be used to map
IPv6 addresses and host names back and forth without recourse to the blocking
functions getaddrinfo() and getnameinfo().

The logical name WASD_TCPIP6_IPNODES allows multiple values to be defined each
of which can represent an IPv4 or IPv6 and then qualified host name (similar to
the content of the IPNODES.DAT file).  The logical name must exist at startup
but after than can have its value(s) changed dynamically.

This is an example:

   $ DEFINE /SYSTEM /EXEC WASD_TCPIP6_IPNODES -
     "fe80::200:f8ff:fe75:c062 klaatu6.private.net",-
     "fe80::21d:7dff:fed3:ae62 gort6.private.net"

If a logical value is "*" (first, last, in-between or only) then the logical
name processing stops and getaddrinfo() getnameinfo() are used allowing AAAA
records (IPv6 DNS host entries) to be processed.  NOTE: the "*" causes
name/address resolution to become blocking adding significant granularity to
server processing.  Of course after resolution, and until the lifetime expires,
the resolved name/address is cached by the calling TCPIP.C functions.

In reality this should have little impact for the average IPv6 server.  It is a
general recommendation not to have client host address-to-name lookup enabled
on a web server (this can be handled during access log processing for example). 
For name-to-address resolution there are two occasions when it is used. 
Firstly, during startup, resolution of service names to addresses.  These are
made using explicit blocking anyway (serialises the startup).  The second usage
is with proxy processing.  The proxy client's proxied-to host name must be
resolved to an address.  For host names that don't resolve, or not quickly,
this has the potential to add significant granularity.  Fortunately (again)
IPv6 proxy serving is a niche environment.


VERSION HISTORY
---------------
18-NOV-2018  MGD  TcpIp6_getaddrinfo() works around unresolved getaddrinfo()
                  TcpIp6_getnameinfo() works around unresolved getnameinfo() 
30-AUG-2010  MGD  initial (needed *something* for IPv6 host resolution)
*/
/*****************************************************************************/

#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

#include <stdio.h>
#include <ctype.h>

#ifndef TCPIP6_STUB

#pragma __nostandard

/* VAX and some earlier Alpha TCP/IP don't support these functions */
extern int getaddrinfo (...);
int (*TcpIp6_getaddrinfo)() = getaddrinfo;
extern int getnameinfo (...);
int (*TcpIp6_getnameinfo)() = getnameinfo;

/* and under HP C V7.3-009 on OpenVMS Alpha V8.4 netdb.h breaks the above */
#include <netdb.h>
#ifndef AI_ALL
/* at least Compaq C V6.4-005 on OpenVMS VAX V7.2 */
#define AI_ALL 0
#endif
#endif

#pragma __standard

#include <lnmdef.h>

#include "wasd.h"
#include "tcpip.h"

#define WASD_MODULE "TCPIP6"

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

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This is somewhat of a KLUDGE function!
*/

BOOL TcpIp6Nodes
(
char *hnaptr,
IPADDRESS *ipaptr
)
{
   static BOOL  IpNodes = true;
   static unsigned long  LnmIndex;
   static $DESCRIPTOR (LogNameDsc, "WASD_TCPIP6_IPNODES");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static unsigned long  LnmAttributes;
   static VMS_ITEM_LIST3
   LnmItems [] =
   {
      { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 },
      { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 },
      { 0, LNM$_STRING, 0, 0 },
      { 0,0,0,0 }
   };

   int  status;
   unsigned short  LogValueLength;
   unsigned long  LnmCount;
   char  *asptr, *cptr, *sptr;
   char  LogValue [256];

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "TcpIp6Nodes() !&B !&Z !UL",
                 IpNodes, hnaptr, IPADDRESS_SIZE(ipaptr));

   if (!IpNodes) return (false);

   LnmItems[2].buf_len = sizeof(LogValue)-1;
   LnmItems[2].buf_addr = LogValue;
   LnmItems[2].ret_len = &LogValueLength;

   if (!*hnaptr) asptr = TcpIpAddressToString (ipaptr, 0);

   for (LnmIndex = 0; LnmIndex <= 127; LnmIndex++)
   {
      status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
      if (VMSnok (status) || !(LnmAttributes & LNM$M_EXISTS))
      {
         if (LnmIndex) break;
         IpNodes = false;
         return (false);
      }
      LogValue[LogValueLength] = '\0';

      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !AZ", LnmIndex, LogValue);

      if (LogValue[0] == '*')
         if (*hnaptr)
            return (TcpIp6GetAddrInfo (hnaptr, ipaptr));
         else
            return (TcpIp6GetNameInfo (hnaptr, ipaptr));

      for (cptr = LogValue; *cptr && !ISLWS(*cptr); cptr++);
      *cptr++ = '\0';
      while (*cptr && ISLWS(*cptr)) cptr++;

      if (*hnaptr)
      {
         /* name to address */
         if (!strsame (hnaptr, cptr, -1)) continue;
         if (VMSok (TcpIpStringToAddress (LogValue, ipaptr)))
            return (true);
         else
            return (false);
      }
      else
      {
         /* address to name */
         if (!strsame (asptr, LogValue, -1)) continue;
         for (sptr = hnaptr; *cptr; *sptr++ = *cptr++);
         *sptr = '\0';
         return (true);
      }
   }

   return (false);
}

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

BOOL TcpIp6GetAddrInfo
(
char *hnaptr,
IPADDRESS *ipaptr
)
{
#ifndef TCPIP6_STUB

   static struct addrinfo  IPv6Hint = { AI_ALL, AF_INET6,
                                        0, 0, 0, NULL, NULL, NULL };
   int  retval;
   struct  addrinfo  *raiptr,
                     *RetAddrInfo;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "TcpIp6GetAddrInfo() !&Z !UL",
                 hnaptr, IPADDRESS_SIZE(ipaptr));

   if (TcpIp6_getaddrinfo == 0)
   {
      static int  reported = 0;
      if (!reported)
         FaoToStdout ("%HTTPD-W-TCPIP6, getaddrinfo() unresolved and disabled\n");
      retval = reported = -1;
   }
   else
      retval = TcpIp6_getaddrinfo (hnaptr, NULL, &IPv6Hint, &RetAddrInfo);

   if (retval) return (false);

   /* just use the first of this (potential) list of addresses */
   raiptr = RetAddrInfo;
   if (raiptr->ai_family == AF_INET)
      IPADDRESS_GET4 (ipaptr,
                      &((struct sockaddr_in*)(raiptr->ai_addr))->sin_addr)
   else
   if (raiptr->ai_family == AF_INET6)
      IPADDRESS_GET6 (ipaptr,
                      &((struct sockaddr_in6*)(raiptr->ai_addr))->sin6_addr)

   freeaddrinfo (&RetAddrInfo);

   return (true);

#else
   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "TcpIp6GetAddrInfo() NOT SUPPORTED");

   return (false);
#endif
}

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

BOOL TcpIp6GetNameInfo
(
char *hnaptr,
IPADDRESS *ipaptr
)
{
#ifndef TCPIP6_STUB

   static int  flags = NI_NAMEREQD;

   int  nodelen, retval;
   struct sockaddr_in6  Sock6Addr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "TcpIp6GetNameInfo() !&Z !UL",
                 hnaptr, IPADDRESS_SIZE(ipaptr));

   /* only used for IPv6 addresses */
   if (!IPADDRESS_IS_V6 (ipaptr)) return (false);

   memset (&Sock6Addr, 0, sizeof(Sock6Addr));
   Sock6Addr.sin6_family = AF_INET6;
   memcpy (Sock6Addr.sin6_addr,
           IPADDRESS_ADR6(ipaptr),
           sizeof(Sock6Addr.sin6_addr));

   if (TcpIp6_getnameinfo == 0)
   {
      static int  reported = 0;
      if (!reported)
         FaoToStdout ("%HTTPD-W-TCPIP6, getnameinfo() unresolved and disabled\n");
      retval = reported = 1;  /* EPERM errno */
   }
   else
      retval = TcpIp6_getnameinfo (&Sock6Addr, sizeof(Sock6Addr),
                                   hnaptr, &nodelen,
                                   NULL, NULL, flags);

   if (retval) return (false);

   return (true);

#else
   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "TcpIp6GetNameInfo() NOT SUPPORTED");

   return (false);
#endif
}

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

