/*****************************************************************************/
/*
                               AuthIdent.c

Licensed under the Apache License, Version 2.0 (the License);
you may not use this software except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This module provides functions related to authorization via the RFC1413
'Identification Protocol'.  Although used for authentication RFC1413 does not
describe itself as such, just as an identification protocol.  The client (this
module) waits a maximum of thirty seconds for a response from the ident server. 
It always closes the connection immediately after processing the response
(mainly because the three ident daemons this module was tested against always
closed the server connection proactively, even though the RFC allows for
multiple requests via the one TCP/IP connect).

The 'RemoteUser' generated by this module comprises the RCF1413 name returned
in the response plus the remote system IP dotted-decimal address, in the
following format: "name@nnn.nnn.nnn.nnn" (which allows approximately 30
characters for the name, which should be ample).

See AUTH.C for overall detail on the WASD authorization environment.


VERSION HISTORY
---------------
27-APR-2021  MGD  BSD 4.4 sockaddr.. IO$M_EXTEND to $QIO
10-JUN-2005  MGD  make EXQUOTA (ASTLM) a little more obvious
10-APR-2004  MGD  modifications to support IPv6
04-AUG-2002  MGD  with DNS lookup use host name to construct remote user
04-AUG-2001  MGD  support module WATCHing
23-APR-2001  MGD  more flexibility in response parsing
20-APR-2001  MGD  bind service address to connecting socket
13-FEB-2001  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 <stdio.h>
#include <string.h>

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

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

#define WASD_MODULE "AUTHIDENT"

#if WATCH_MOD
#define FI_NOLI WASD_MODULE, __LINE__
#else
/* in production let's keep the exact line to ourselves! */
#define FI_NOLI WASD_MODULE, 0
#endif

#define AUTH_IDENT_PORT 113

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

extern int  EfnWait,
            EfnNoWait;

extern const int64  Delta30Sec;

extern char  ErrorSanityCheck [];

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern TCP_SOCKET_ITEM  TcpIpSocket4,
                        TcpIpSocket6;
extern VMS_ITEM_LIST2  TcpIpFullDuplexCloseOption;
extern WATCH_STRUCT  Watch;

/****************************************************************************/
/*
Begin RFC1413 identification.
Authorization's all asynchronous from here on in.
*/ 

void AuthIdentBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction
)
{
   static BOOL  UseFullDuplexClose = true;

   int  cnt, qiofun, status;
   AUTH_IDENT  *idptr;
   CLIENT_STRUCT  *clptr;
   SOCKADDRESS  SocketName;
   SOCKADDRIN  *sin4ptr;
   SOCKADDRIN6  *sin6ptr;
   TCP_SOCKET_ITEM  *TcpSocketPtr;
   VMS_ITEM_LIST2  SocketNameItem;
   VMS_ITEM_LIST2  *il2ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentBegin() !&A !&I",
                 AstFunction, &rqptr->ClientPtr->IpAddress);

   clptr = rqptr->ClientPtr;

   /* after calling this function authorization completes asynchronously! */
   rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer;
   rqptr->rqAuth.FinalStatus = AUTH_PENDING;

   /* no need to free this as it will be disposed of at request run-down */
   idptr = VmGetHeap (rqptr, sizeof(AUTH_IDENT));
   idptr->RequestPtr = rqptr;
   idptr->AuthAstFunction = AstFunction;

   if (WATCHING (rqptr, WATCH_AUTH))
      WatchThis (idptr->RequestPtr, WATCH_AUTH,
                 "RFC1413 connect !AZ to !AZ,!UL",
                 rqptr->ServicePtr->ServerIpAddressString,
                 &clptr->IpAddressString, AUTH_IDENT_PORT);

   /* assign a channel to the internet template device */
   if (VMSnok (status =
       sys$assign (&TcpIpDeviceDsc, &idptr->IdentChannel, 0, 0)))
   {
      /* leave it to the AST function to report! */
      idptr->ConnectIOsb.Status = status;
      SysDclAst (AuthIdentConnectAst, idptr);
      return;
   }

   /* bind to the IP address of the service (for multi-homed hosts) */
   if (IPADDRESS_IS_V4 (&rqptr->ServicePtr->ServerIpAddress))
   {
      SOCKADDRESS_ZERO4 (&SocketName);
      sin4ptr = &SocketName.sa.v4; 
      sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET;
      sin4ptr->SIN$W_PORT = 0;  /* driver allocates port */
      IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &rqptr->ServicePtr->ServerIpAddress)

      il2ptr = &SocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = &SocketName.sa.v4;
   }
   else
   if (IPADDRESS_IS_V6 (&rqptr->ServicePtr->ServerIpAddress))
   {
      SOCKADDRESS_ZERO6 (&SocketName);
      sin6ptr = &SocketName.sa.v6; 
      memset (sin6ptr, 0, sizeof(SOCKADDRIN6));
      sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6;
      sin6ptr->SIN6$W_PORT = 0;  /* driver allocates port */
      IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR,
                      &rqptr->ServicePtr->ServerIpAddress)

      il2ptr = &SocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN6);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = &SocketName.sa.v6;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (IPADDRESS_IS_V4 (&clptr->IpAddress))
   {
      TcpSocketPtr = &TcpIpSocket4;
      qiofun = IO$_SETMODE;
   }
   else
   if (IPADDRESS_IS_V6 (&clptr->IpAddress))
   {
      TcpSocketPtr = &TcpIpSocket6;
      qiofun = IO$_SETMODE | IO$M_EXTEND;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* make the channel a TCP, connection-oriented, address-bound socket */
   if (UseFullDuplexClose)
   {
      status = sys$qiow (EfnWait, idptr->IdentChannel,
                         qiofun, &idptr->ConnectIOsb, 0, 0,
                         TcpSocketPtr, 0, &SocketNameItem,
                         0, &TcpIpFullDuplexCloseOption, 0);

      /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, try without */
      if (VMSok (status) && VMSnok (idptr->ConnectIOsb.Status))
      {
         /* deassign existing channel, assign a new channel, before retrying */
         sys$dassgn (idptr->IdentChannel);
         if (VMSnok (status =
             sys$assign (&TcpIpDeviceDsc, &idptr->IdentChannel, 0, 0)))
         {
            /* leave it to the AST function to report! */
            idptr->ConnectIOsb.Status = status;
            SysDclAst (AuthIdentConnectAst, idptr);
            return;
         }
      }
   }

   if (!UseFullDuplexClose ||
       VMSok (status) && VMSnok (idptr->ConnectIOsb.Status))
   {
      /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, use without */
      status = sys$qiow (EfnWait, idptr->IdentChannel,
                         qiofun, &idptr->ConnectIOsb, 0, 0,
                         TcpSocketPtr, 0, &SocketNameItem, 0, 0, 0);

      /* seeing this worked OK let's not bother with the first one again */
      if (UseFullDuplexClose &&
          VMSok (status) && VMSok (idptr->ConnectIOsb.Status))
         UseFullDuplexClose = false;
   }

   if (VMSok (status) && VMSnok (idptr->ConnectIOsb.Status))
      status = idptr->ConnectIOsb.Status;
   if (VMSnok (status))
   {
      /* leave it to the AST function to report! */
      idptr->ConnectIOsb.Status = status;
      SysDclAst (AuthIdentConnectAst, idptr);
      return;
   }

   /* now the destination address and port details */
   if (IPADDRESS_IS_V4 (&clptr->IpAddress))
   {
      SOCKADDRESS_ZERO4 (&SocketName);
      sin4ptr = &SocketName.sa.v4;
      sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET;
      sin4ptr->SIN$W_PORT = htons (AUTH_IDENT_PORT);
      IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &clptr->IpAddress)

      il2ptr = &SocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = sin4ptr;

      qiofun = IO$_ACCESS;
   }
   else
   if (IPADDRESS_IS_V6 (&clptr->IpAddress))
   {
      SOCKADDRESS_ZERO6 (&SocketName);
      sin6ptr = &SocketName.sa.v6;
      sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6;
      sin6ptr->SIN6$W_PORT = htons (AUTH_IDENT_PORT);
      IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR,
                      &clptr->IpAddress)

      il2ptr = &SocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = sin6ptr;

      qiofun = IO$_ACCESS | IO$M_EXTEND;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qio (EfnNoWait, idptr->IdentChannel,
                     qiofun, &idptr->ConnectIOsb, &AuthIdentConnectAst, idptr,
                     0, 0, &SocketNameItem, 0, 0, 0);

   /* if OK return waiting for the connect AST to be delivered */
   if (VMSok (status)) return;

   /* if resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
   {
      /* no ASTs means not much of anything else can happen so just exit! */
      sys$canexh(0);
      /* make the message a little more meaningful */
      sys$exit (SS$_EXASTLM);
   }

   /* connect failed, call AST explicitly, status in the IOsb */
   idptr->ConnectIOsb.Status = status;
   SysDclAst (AuthIdentConnectAst, idptr);
}

/****************************************************************************/
/*
Connection to ident daemon has completed (successfully or not).
*/

AuthIdentConnectAst (AUTH_IDENT *idptr)

{
   int  status;
   unsigned short  Length;
   char  *cptr;
   CLIENT_STRUCT  *clptr;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = idptr->RequestPtr;
   clptr = rqptr->ClientPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
"AuthIdentConnectAst() !&F !&S", &AuthIdentConnectAst,
                 idptr->ConnectIOsb.Status);

   if (VMSnok (idptr->ConnectIOsb.Status))
   {
      /*****************/
      /* connect error */
      /*****************/

      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "RFC1413 connect !&S",
                    idptr->ConnectIOsb.Status);

      /* dispose of the non-connected channel */
      AuthIdentCloseSocket (idptr);

      /* continue to process the authorization */
      rqptr->rqAuth.FinalStatus = idptr->ConnectIOsb.Status;
      /* let's renegotiate with the client, trying to get a certificate */
      SysDclAst (idptr->AuthAstFunction, rqptr);
      return;
   }

   /***********************/
   /* write ident request */
   /***********************/

   FaoToBuffer (idptr->RequestString, sizeof(idptr->RequestString), &Length,
                "!UL, !UL\r\n",
                clptr->IpPort, rqptr->ServicePtr->ServerPort);

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 request !UL bytes", Length);
      WatchDataDump (idptr->RequestString, Length);
   }

   status = sys$qiow (EfnWait, idptr->IdentChannel,
                      IO$_WRITEVBLK, &idptr->WriteIOsb, 0, 0,
                      idptr->RequestString, Length, 0, 0, 0, 0);

   if (VMSok (status)) status = idptr->WriteIOsb.Status;
   if (VMSnok (status))
   {
      /* not OK, becomes the final status, continue processing */
      rqptr->rqAuth.FinalStatus = idptr->WriteIOsb.Status;
      SysDclAst (idptr->AuthAstFunction, rqptr);
      return;
   }

   /***********************/
   /* read ident response */
   /***********************/

   status = sys$qio (EfnNoWait, idptr->IdentChannel,
                     IO$_READVBLK, &idptr->ReadIOsb,
                     &AuthIdentReadAst, idptr,
                     idptr->ResponseString, sizeof(idptr->ResponseString)-1,
                     0, 0, 0, 0);

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "RFC1413 $qio() read");

   if (VMSok (status))
   {
      if (VMSnok (status =
          sys$setimr (0, &Delta30Sec, &AuthIdentReadTimeoutAst,
                      idptr, 0)))
         ErrorExitVmsStatus (status, NULL, FI_LI);

      if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                    "RFC1413 $setimr() 30 seconds");
      return;
   }

   /* if resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
   {
      /* no ASTs means not much of anything else can happen so just exit! */
      sys$canexh(0);
      /* make the message a little more meaningful */
      sys$exit (SS$_EXASTLM);
   }

   /* read failed, call AST explicitly, status in the IOsb */
   idptr->ReadIOsb.Status = status;
   idptr->ReadIOsb.Count = 0;
   SysDclAst (AuthIdentReadAst, idptr);
}

/*****************************************************************************/
/*
Ident daemon has not responded within 30 seconds.
*/ 

AuthIdentReadTimeoutAst (AUTH_IDENT *idptr)

{
   REQUEST_STRUCT  *rqptr;

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

   rqptr = idptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthIdentReadTimeoutAst() !&F", &AuthIdentReadTimeoutAst);

   sys$cancel (idptr->IdentChannel);
}

/*****************************************************************************/
/*
The ident daemon has responded.
*/ 

AuthIdentReadAst (AUTH_IDENT *idptr)

{
   int  status,
        LocalPort,
        RemotePort,
        UserDetailsLength;
   char  *cptr, *sptr, *zptr;
   char  UserDetails [64];
   CLIENT_STRUCT  *clptr;
   REQUEST_STRUCT *rqptr;

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

   rqptr = idptr->RequestPtr;
   clptr = rqptr->ClientPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthIdentReadAst() !&F !&X !UL", &AuthIdentReadAst,
                 idptr->ReadIOsb.Status, idptr->ReadIOsb.Count);

   /* cancel the time and close the socket */
   sys$cantim (idptr, 0);

   if (VMSok (idptr->ReadIOsb.Status))
   {
      /* zero bytes with a normal status is a definite no-no (TGV-Multinet) */
      if (!idptr->ReadIOsb.Count) idptr->ReadIOsb.Status = SS$_ABORT;
   }

   if (VMSnok (idptr->ReadIOsb.Status))
   {
      /* read error */
      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "RFC1413 read !AZ,!UL !&S",
                    &clptr->IpAddressString, AUTH_IDENT_PORT,
                    idptr->ReadIOsb.Status);

      AuthIdentCloseSocket (idptr);

      /* continue to process the authorization */
      rqptr->rqAuth.FinalStatus = idptr->ReadIOsb.Status;
      SysDclAst (idptr->AuthAstFunction, rqptr);
      return;
   }

   idptr->ResponseString[idptr->ReadIOsb.Count] = '\0';

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 response !UL bytes",
                 idptr->ReadIOsb.Count);
      WatchDataDump (idptr->ResponseString, idptr->ReadIOsb.Count);
   }

   AuthIdentCloseSocket (idptr);

   /**********************/
   /* parse the response */
   /**********************/

   /* format: "[ ]rem-port[ ],[ ]local-port[ ]:[ ]USERID[ ]:[ ]name[ ]" */
   RemotePort = LocalPort = 0;
   for (cptr = idptr->ResponseString; *cptr && isspace(*cptr); cptr++);
   if (isdigit(*cptr)) RemotePort = atoi(cptr);
   while (*cptr && isdigit(*cptr)) cptr++;
   while (*cptr && (isspace(*cptr) || *cptr == ',')) cptr++;
   if (isdigit(*cptr)) LocalPort = atoi(cptr);
   while (*cptr && isdigit(*cptr)) cptr++;
   while (*cptr && (isspace(*cptr) || *cptr == ':')) cptr++;
   if (strsame (cptr, "USERID", 6))
   {
      while (*cptr && *cptr != ':') cptr++;
      if (*cptr) cptr++;
      /* "user details" - operating system */
      while (*cptr && isspace(*cptr)) cptr++;
      zptr = (sptr = UserDetails) + sizeof(UserDetails)-1;
      while (*cptr && !isspace(*cptr) && *cptr != ':' && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      UserDetailsLength = sptr - UserDetails;
      while (*cptr && (isspace(*cptr) || *cptr == ':')) cptr++;
      /* identifier */
      zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser);
      while (*cptr && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = '@';
      if (!*(cptr = clptr->Lookup.HostName))
         cptr = &clptr->IpAddressString;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++; 
      if (sptr >= zptr)
      {
         sptr = rqptr->RemoteUser;
         ErrorGeneralOverflow (rqptr, FI_LI);
      }
      *sptr = '\0';
      rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;
   
      if (rqptr->RemoteUserLength &&
          (RemotePort != clptr->IpPort ||
           LocalPort != rqptr->ServicePtr->ServerPort))
      {
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 response ports?");
         rqptr->RemoteUser[0] = '\0';
         rqptr->RemoteUserLength = 0;
      }
   }

   if (rqptr->RemoteUserLength)
   {
      /* authenticated ... user can do anything (the path allows!) */
      rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;

      rqptr->rqAuth.UserDetailsLength = UserDetailsLength;
      rqptr->rqAuth.UserDetailsPtr = VmGetHeap (rqptr, UserDetailsLength+1);
      strzcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails, UserDetailsLength+1);
      strzcpy (rqptr->RemoteUserPassword, "anystringwilldo",
               sizeof(rqptr->RemoteUserPassword));
   }
   else
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_FAIL;

   /* continue to process the authorization */
   SysDclAst (idptr->AuthAstFunction, rqptr);
}

/****************************************************************************/
/*
Just shut the socket down, bang!
*/

AuthIdentCloseSocket (AUTH_IDENT *idptr)

{
   int  status;
   REQUEST_STRUCT *rqptr;

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

   rqptr = idptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthIdentCloseSocket() !&F", &AuthIdentCloseSocket);

   status = sys$dassgn (idptr->IdentChannel);

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      if (VMSok(status))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "RFC1413 close !AZ,!UL",
                    rqptr->ClientPtr->IpAddressString,
                    AUTH_IDENT_PORT);
      else
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "RFC1413 close !AZ,!UL !&S",
                    rqptr->ClientPtr->IpAddressString,
                    AUTH_IDENT_PORT, status);
   }
}

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

