/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                  wsLIBcl.c

An additional component of the wsLIB WebSocket library for WASD persistent
WebSocket scripts.  This module provides functionality to connect to a server
host and port and undertake a WebSocket upgrade handshake.  This establishes a
TCP/IP socket that WSLIB.C functions recognise and can use to support client
WebSocket actions.  It is less intended to be a real-world client library than
a vehicle for bench-testing the WASD WebSocket and WSLIB.C functionality.

See WS_BENCHC.C utility for a usage example.


FUNCTIONS
---------

int WsLibClConnect
(
struct WsLibStruct *wsptr,
char *ServerHost,
int ServerPort,
char *RequestUri,
void *AstFunction
)

   Connects via clear-text TCP/IP to the specified server host and port
   and performs a WebSocket upgrade handshake.


int WsLibClSocketStatus (struct WsLibStruct *wsptr)

   Returns the the current (most recent) TCP/IP socket status.
   This is essentially the VMS status of the HTTP WebSocket upgrade request.


COPYRIGHT
---------
Copyright (C) 2011-2020 Mark G.Daniel
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY
---------------
06-DEC-2014  MGD  WsLibcl__Init()
06-FEB-2011  MGD  WsLibCl__ConnAst() port 8080 kludge for proxy development
13-JUN-2011  MGD  initial
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <descrip.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

/* IP-related header files */
#include <socket.h>
#include <in.h>
#include <netdb.h>
#include <inet.h>

#include "../httpd/base64.h"
#include "../httpd/sha1.h"
#include "wslib.h"

#ifndef EFN$C_ENF
#  define EFN$C_ENF 128  /* Event No Flag (no stored state) */
#endif

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define FI_LI "WSLIBCL", __LINE__

#if 1
#define WATCH_WSLIB if(wsptr->WatchScript)WsLibWatchScript
#else
#define WATCH_WSLIB if(0)WsLibWatchScript
#endif

/* can be set non-zero by external module, see WsLibClBreakNow() */
int  WsLibClBreakEvery = 0,
     WsLibClBreakCount;

static $DESCRIPTOR (InetDeviceDsc, "UCX$DEVICE");

static int  OptionEnabled = 1,
            EfnWait,
            EfnNoWait;

static struct {
   unsigned short  Length;
   unsigned short  Parameter;
   char  *Address;
} ReuseAddr =
   { sizeof(OptionEnabled), TCPIP$C_REUSEADDR, (void*)&OptionEnabled },
  NoDelaysAtAll [] = {
    { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled },
    { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled },
  },
  NoDelAck =
   { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled },
  NoDelay =
   { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled },
  ReuseAddressSocketOption =
   { sizeof(ReuseAddr), TCPIP$C_SOCKOPT, (void*)&ReuseAddr },
  NoDelAckTcpOption =
   { sizeof(NoDelAck), TCPIP$C_TCPOPT, (void*)&NoDelAck },
  NoDelayTcpOption =
   { sizeof(NoDelay), TCPIP$C_TCPOPT, (void*)&NoDelay },
  NoDelaysAtAllTcpOption =
   { sizeof(NoDelaysAtAll), TCPIP$C_TCPOPT, (void*)&NoDelaysAtAll };

static struct {
   unsigned short  Protocol;
   unsigned char  Type;
   unsigned char  Family;
} TcpSocket = { TCPIP$C_TCP, INET_PROTYP$C_STREAM, TCPIP$C_AF_INET };

/***
#define __VAX
#undef __ALPHA
***/

/*****************************************************************************/
/*
Make a TCP/IP connection to the specified HTTP server name and port.  When
network connected send an HTTP request to upgrade the connection to WebSocket. 
If an AST function is supplied then this function completes asynchronously.
*/
 
int WsLibClConnect
(
struct WsLibStruct *wsptr,
char *ServerHost,
int ServerPort,
char *RequestUri,
void *AstFunction
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   struct hostent  *HostEntryPtr;

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

   if (getenv("WATCH_SCRIPT")) wsptr->WatchScript = 1;

   WsLibCl__Init ();

   if (!ServerPort) ServerPort = 80;

   HostEntryPtr = gethostbyname (ServerHost);
   if (!HostEntryPtr) return (vaxc$errno);

   /* allocate required storage */
   wsptr->ClientAcceptSize = 64;
   wsptr->ClientAcceptPtr = calloc (1, wsptr->ClientAcceptSize);
   if (!wsptr->ClientAcceptPtr) return (vaxc$errno);

   wsptr->ClientHeaderSize = 512;
   wsptr->ClientHeaderPtr = calloc (1, wsptr->ClientHeaderSize);
   if (!wsptr->ClientHeaderPtr) return (vaxc$errno);

   wsptr->ClientKeySize = 32;
   wsptr->ClientKeyPtr = calloc (1, wsptr->ClientKeySize);
   if (!wsptr->ClientKeyPtr) return (vaxc$errno);

   wsptr->ClientServerSize = 128;
   wsptr->ClientServerPtr = calloc (1, wsptr->ClientServerSize);
   if (!wsptr->ClientServerPtr) return (vaxc$errno);

   wsptr->ClientUriSize = 256;
   wsptr->ClientUriPtr = calloc (1, wsptr->ClientUriSize);
   if (!wsptr->ClientUriPtr) return (vaxc$errno);

   wsptr->SocketName.sin_family = HostEntryPtr->h_addrtype;
   wsptr->SocketName.sin_port = htons (ServerPort);
   wsptr->SocketName.sin_addr = *((struct in_addr *)HostEntryPtr->h_addr);
   wsptr->SocketNameItem[0] = sizeof(wsptr->SocketName);
   wsptr->SocketNameItem[1] = (int)&wsptr->SocketName;

   if (strchr (HostEntryPtr->h_name, '.'))
      cptr = HostEntryPtr->h_name;
   else
      cptr = ServerHost;
   zptr = (sptr = wsptr->ClientServerPtr) + wsptr->ClientServerSize-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   wsptr->ClientServerPort = ServerPort;

   zptr = (sptr = wsptr->ClientUriPtr) + wsptr->ClientUriSize-1;
   for (cptr = RequestUri; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   /* assign a channel to the internet template device */
   status = sys$assign (&InetDeviceDsc, &wsptr->SocketChannel, 0, 0);
   if (VMSnok (status)) return (status);

   /* make the channel a TCP, connection-oriented socket */
   status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_SETMODE,
                      &wsptr->SocketIOsb, 0, 0,
                      &TcpSocket, 0, 0, 0, &ReuseAddressSocketOption, 0);
   if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status;
   if (VMSnok (status))
   {
      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      return (status);
   }

   /* turn off Nagle algorithm and acknowlegement delay (the default) */
   status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_SETMODE,
                      &wsptr->SocketIOsb, 0, 0,
                      0, 0, 0, 0, &NoDelaysAtAllTcpOption, 0);
   if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status;
   if (VMSnok (status))
   {
      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      return (status);
   }

   if (wsptr->ConnectAstFunction = AstFunction)
      status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_ACCESS,
                        &wsptr->SocketIOsb, &WsLibCl__ConnAst, wsptr,
                        0, 0, &wsptr->SocketNameItem, 0, 0, 0);
   else
   {
      status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_ACCESS,
                         &wsptr->SocketIOsb, &WsLibCl__ConnAst, wsptr,
                         0, 0, &wsptr->SocketNameItem, 0, 0, 0);
      if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status;
   }

   return (status);
}

/*****************************************************************************/
/*
Connection to server has completed (either successfully or unsuccessfully).
*/
 
static void WsLibCl__ConnAst (struct WsLibStruct *wsptr)

{
   static $DESCRIPTOR (HeaderFaoDsc,
"GET !AZ HTTP/1.1\r\n\
Host: !AZ!AZ\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Key: !AZ\r\n\
Sec-WebSocket-Version: !UL\r\n\
\r\n\0");
   static $DESCRIPTOR (HeaderDsc, "");

   static unsigned long  RandomNumber,
                         RandomFiller;

   int  cnt, status,
        Base64Len;
   unsigned short  HeaderLength;
   unsigned long  Key16 [4];
   char  KeyGuid [96],
         HostPort [16];
   unsigned char  KeyGuidHash [20];
   SHA1Context  Sha1Ctx;

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

   status = wsptr->SocketIOsb.iosb$w_status;

   if (VMSnok (wsptr->SocketIOsb.iosb$w_status))
   {
      /*****************/
      /* connect error */
      /*****************/

      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      if (wsptr->ConnectAstFunction)
      {
         sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0);
         wsptr->ConnectAstFunction = NULL;
      }
      return;
   }

   /**********************/
   /* build HTTP upgrade */
   /**********************/

   /* pseudo-random 16 bytes */
   if (!(RandomNumber & 0xff)) sys$gettim (&RandomNumber);
   for (cnt = 0; cnt < 4; cnt++)
      Key16[cnt] = RandomNumber = RandomNumber * 69069 + 1;

   /* this will be the Sec-WebSocket-Key: field */
   Base64Len = wsptr->ClientKeySize;
   base64_encode ((unsigned char*)wsptr->ClientKeyPtr, &Base64Len,
                  (unsigned char*)Key16, 16);

   /* also generate the expected server Sec-WebSocket-Accept: value */
   strcpy (KeyGuid, wsptr->ClientKeyPtr);
   strcat (KeyGuid, WSLIB_GUID);

   /* which will be used to ... */
   SHA1Reset (&Sha1Ctx);
   SHA1Input (&Sha1Ctx, (unsigned char*)KeyGuid, strlen(KeyGuid));
   if (!SHA1Result (&Sha1Ctx)) WsLibExit (NULL, FI_LI, SS$_BUGCHECK);
   /* copy into the hash buffer (converting from big to little endian) */
   SHA1LitEnd (&Sha1Ctx, KeyGuidHash);

   /* ... compare to the server response Sec-WebSocket-Accept: */
   Base64Len = wsptr->ClientAcceptSize;
   base64_encode ((unsigned char*)wsptr->ClientAcceptPtr, &Base64Len,
                  (unsigned char*)KeyGuidHash, sizeof(KeyGuidHash));

   if (wsptr->ClientServerPort == 80 ||
       wsptr->ClientServerPort == 8080)
      HostPort[0] = '\0';
   else
      sprintf (HostPort, ":%d", wsptr->ClientServerPort);

   HeaderDsc.dsc$a_pointer = wsptr->ClientHeaderPtr;
   HeaderDsc.dsc$w_length = wsptr->ClientHeaderSize;

   status = sys$fao (&HeaderFaoDsc, &HeaderLength, &HeaderDsc,
                     wsptr->ClientUriPtr,
                     wsptr->ClientServerPtr, HostPort,
                     wsptr->ClientKeyPtr,
                     wsptr->WebSocketVersion);
   if (VMSnok (status)) WsLibExit (NULL, FI_LI, status);

   /* not including the null terminator */
   HeaderLength--;

   /****************/
   /* send request */
   /****************/

   WATCH_WSLIB (wsptr, FI_LI, "!#AZ", HeaderLength, wsptr->ClientHeaderPtr);

   if (wsptr->ConnectAstFunction)
      status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_WRITEVBLK,
                        &wsptr->SocketIOsb, WsLibCl__ConnRequAst, wsptr,
                        wsptr->ClientHeaderPtr, HeaderLength, 0, 0, 0, 0);
   else
      status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_WRITEVBLK,
                         &wsptr->SocketIOsb, WsLibCl__ConnRequAst, wsptr,
                         wsptr->ClientHeaderPtr, HeaderLength, 0, 0, 0, 0);

   WsLibClBreakNow (wsptr);
}

/*****************************************************************************/
/*
Request header write has completed (either successfully or unsuccessfully).
*/
 
static void WsLibCl__ConnRequAst (struct WsLibStruct *wsptr)

{
   int  status;

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

   if (VMSnok (wsptr->SocketIOsb.iosb$w_status))
   {
      /* write error */
      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      if (wsptr->ConnectAstFunction)
      {
         sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0);
         wsptr->ConnectAstFunction = NULL;
      }
      return;
   }

   /* peek at response header */
   if (wsptr->ConnectAstFunction)
      status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_READLBLK,
                        &wsptr->SocketIOsb, WsLibCl__ConnResp1Ast, wsptr,
                        wsptr->ClientHeaderPtr, wsptr->ClientHeaderSize-1,
                        0, TCPIP$C_MSG_PEEK, 0, 0);
   else
      status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_READLBLK,
                         &wsptr->SocketIOsb, WsLibCl__ConnResp1Ast, wsptr,
                         wsptr->ClientHeaderPtr, wsptr->ClientHeaderSize-1,
                         0, TCPIP$C_MSG_PEEK, 0, 0);
}

/*****************************************************************************/
/*
Assume the response header is complete in the single peek!  Calculate the
length of the response header and then read just that many bytes.  This appears
necessary because at least the Autobahn test suite sends the first frame at
the same time as the response header.  Orchestrating reads with $QIO means this
peek+read is the most practical way to negotiate past the response header.
*/
 
void WsLibCl__ConnResp1Ast (struct WsLibStruct *wsptr)

{
   int  status;
   char  *cptr;

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

   WATCH_WSLIB (wsptr, FI_LI, "PEEK %X!8XL", wsptr->SocketIOsb.iosb$w_status);

   if (VMSnok (wsptr->SocketIOsb.iosb$w_status))
   {
      /**************/
      /* read error */
      /**************/

      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      if (wsptr->ConnectAstFunction)
      {
         sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0);
         wsptr->ConnectAstFunction = NULL;
      }
      return;
   }

   /* scan down to the header-terminating empty line */
   wsptr->ClientHeaderPtr[wsptr->SocketIOsb.iosb$w_bcnt] = '\0';
   cptr = wsptr->ClientHeaderPtr;
   while (*cptr)
   {
      while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++;
      if (*cptr == '\r') cptr++;
      if (*cptr == '\n') cptr++;
      if (*cptr != '\r') continue;
      cptr++;
      if (*cptr != '\n') continue;
      cptr++;
      break;
   }

   /* read response header */
   if (wsptr->ConnectAstFunction)
      status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_READLBLK,
                        &wsptr->SocketIOsb, WsLibCl__ConnResp2Ast, wsptr,
                        wsptr->ClientHeaderPtr, cptr - wsptr->ClientHeaderPtr,
                        0, 0, 0, 0);
   else
      status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_READLBLK,
                         &wsptr->SocketIOsb, WsLibCl__ConnResp2Ast, wsptr,
                         wsptr->ClientHeaderPtr, cptr - wsptr->ClientHeaderPtr,
                         0, 0, 0, 0);
}

/*****************************************************************************/
/*
Assume the response header is complete in the single read!
*/
 
void WsLibCl__ConnResp2Ast (struct WsLibStruct *wsptr)

{
   int  ConUpgrade,
        EndOfHeader,
        Http11,
        HttpStatus,
        SecAccept,
        UpgradeWebSocket;
   char  *cptr, *lptr, *sptr, *zptr,
         *HttpStatusPtr;

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

   WATCH_WSLIB (wsptr, FI_LI, "READ %X!8XL", wsptr->SocketIOsb.iosb$w_status);

   if (VMSnok (wsptr->SocketIOsb.iosb$w_status))
   {
      /**************/
      /* read error */
      /**************/

      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
      if (wsptr->ConnectAstFunction)
      {
         sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0);
         wsptr->ConnectAstFunction = NULL;
      }
      return;
   }

   ConUpgrade = EndOfHeader = Http11 = SecAccept = UpgradeWebSocket = 0;

   wsptr->ClientHeaderPtr[wsptr->SocketIOsb.iosb$w_bcnt] = '\0';
   cptr = wsptr->ClientHeaderPtr;

   WATCH_WSLIB (wsptr, FI_LI, "!AZ", cptr);

   /* response header line */
   if (!memcmp (cptr, "HTTP/1.1", 8)) Http11 = 1;
   while (*cptr && *cptr != ' ' && *cptr != '\r' && *cptr != '\n') cptr++;
   while (*cptr == ' ' && *cptr != '\r' && *cptr != '\n') cptr++;
   HttpStatus = atoi(cptr);
   HttpStatusPtr = cptr;
   while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++;
   if (*cptr) *cptr++ = '\0';

   while (*cptr)
   {
      lptr = cptr;

      if (toupper(*cptr) == 'C' &&
          !strncmp (cptr, "Connection:", 11))
      {
         cptr += 11;
         while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
         wsptr->ServerConnectionPtr = cptr;
         while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++;
         if (*cptr) *cptr++ = '\0';
         if (strstr (wsptr->ServerConnectionPtr, "Upgrade") ||
             strstr (wsptr->ServerConnectionPtr, "upgrade"))
            ConUpgrade = 1;
      }
      else
      if (toupper(*cptr) == 'S' &&
          !strncmp (cptr, "Sec-WebSocket-Accept:", 21))
      {
         cptr += 21;
         while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
         wsptr->ServerAcceptPtr = cptr;
         while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++;
         if (*cptr) *cptr++ = '\0';
         if (!strcmp (wsptr->ServerAcceptPtr, wsptr->ClientAcceptPtr))
            SecAccept = 1;
      }
      else
      if (toupper(*cptr) == 'S' &&
          !strncmp (cptr, "Server:", 7))
      {
         cptr += 7;
         while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
         wsptr->ServerSoftwarePtr = cptr;
         while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++;
         if (*cptr) *cptr++ = '\0';
      }
      else
      if (toupper(*cptr) == 'U' &&
          !strncmp (cptr, "Upgrade:", 8))
      {
         cptr += 8;
         while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
         wsptr->ServerUpgradePtr = cptr;
         while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++;
         if (*cptr) *cptr++ = '\0';
         if (strstr (wsptr->ServerUpgradePtr, "websocket"))
            UpgradeWebSocket = 1;
      }

      while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++;
      if (*cptr == '\r') cptr++;
      if (*cptr == '\n') cptr++;
      if (!*cptr) break;
      if (*(USHORTPTR)cptr == '\r\n' || *cptr == '\n' )
      {
         EndOfHeader = 1;
         break;
      }
   }

   if (!Http11 || HttpStatus != 101 || !EndOfHeader)
      wsptr->SocketIOsb.iosb$w_status = SS$_PROTOCOL;
   else
   if (!ConUpgrade || !SecAccept || !UpgradeWebSocket)
      wsptr->SocketIOsb.iosb$w_status = SS$_PROTOCOL;
   else
      wsptr->SocketIOsb.iosb$w_status = SS$_NORMAL;

   if (VMSok (wsptr->SocketIOsb.iosb$w_status))
   {
      /* once connected channels become synonymous */
      wsptr->InputChannel = wsptr->OutputChannel = wsptr->SocketChannel;
   }
   else
   {
      sys$dassgn (wsptr->SocketChannel);
      wsptr->SocketChannel = 0;
   }

   if (wsptr->ConnectAstFunction)
   {
      sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0);
      wsptr->ConnectAstFunction = NULL;
   }

   WsLibClBreakNow (wsptr);
}

/*****************************************************************************/
/*
Return the socket status value (essentially connect status).
*/

int WsLibClSocketStatus (struct WsLibStruct *wsptr)

{
   /*********/
   /* begin */
   /*********/

   return (wsptr->SocketIOsb.iosb$w_status);
}

/*****************************************************************************/
/*
Set the maxiumum record szie for the WebSocket.  Return the previous value.
*/

int WsLibClSetSocketMrs
(
struct WsLibStruct *wsptr,
int MaxRecSize
)
{
   int  PrevMaxRecSize;

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

   PrevMaxRecSize = wsptr->OutputMrs;
   if ((wsptr->InputMrs = wsptr->OutputMrs = MaxRecSize) > 65535)
      wsptr->InputMrs = wsptr->OutputMrs = 65535;
   return (PrevMaxRecSize);
}

/****************************************************************************/
/*
At the specified proportion randomly sever the connection abruptly and return
true, else return false if connection not broken.  This function really only
exists to support the exercisor or WS_BENCHC.C test-bench utility.
*/

int WsLibClBreakNow (struct WsLibStruct *wsptr)

{
   static int  RandomNumber,
               RandomFiller;

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

   if (!WsLibClBreakEvery) return (FALSE);
   if (!RandomNumber) sys$gettim (&RandomNumber);
   RandomNumber = RandomNumber * 69069 + 1;
   if (RandomNumber % WsLibClBreakEvery) return (FALSE);
   Shut (wsptr);
   WsLibClBreakCount++;
   return (TRUE);
}

/*****************************************************************************/
/*
Initialise the client library.
*/

void WsLibCl__Init ()

{
   static char  GetSyiVer [8];
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      void  *buf_addr;
      void  *ret_len;
   } SyiItem [] =
   {
     { sizeof(GetSyiVer)-1, SYI$_VERSION, &GetSyiVer, 0 },
     { 0,0,0,0 }
   };

   int  status,
        VersionInteger;

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

   /* just the once! */
   if (EfnWait) return;

   status = sys$getsyiw (0, 0, 0, &SyiItem, 0, 0, 0);
   if (VMSnok (status)) WsLibExit (NULL, FI_LI, status);
   VersionInteger = ((GetSyiVer[1]-48) * 100) + ((GetSyiVer[3]-48) * 10);
   if (GetSyiVer[4] == '-') VersionInteger += GetSyiVer[5]-48;
   if (VersionInteger >= 700)
      EfnWait = EfnNoWait = EFN$C_ENF;
   else
   {
      if (VMSnok (status = lib$get_ef (&EfnWait)))
         WsLibExit (NULL, FI_LI, status);;
      if (VMSnok (status = lib$get_ef (&EfnNoWait)))
         WsLibExit (NULL, FI_LI, status);;
   }
}

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

