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


    THE GNU GENERAL PUBLIC LICENSE APPLIES DOUBLY TO ANYTHING TO DO WITH
                    AUTHENTICATION AND AUTHORIZATION!

    This package is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 2 of the License, or any later
    version.

>   This package is distributed in the hope that it will be useful,
>   but WITHOUT ANY WARRANTY; without even the implied warranty of
>   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>   GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

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
---------------
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 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,
        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$W_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;
   else
   if (IPADDRESS_IS_V6 (&clptr->IpAddress))
      TcpSocketPtr = &TcpIpSocket6;
   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, IO$_SETMODE,
                         &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, IO$_SETMODE,
                         &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$W_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;
   }
   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;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qio (EfnNoWait, idptr->IdentChannel, IO$_ACCESS,
                     &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)

{
   static unsigned long  ThirtySecondsDelta [2] = { -300000000, -1 };

   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, &ThirtySecondsDelta, &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);
      strcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails);
      strcpy (rqptr->RemoteUserPassword, "anystringwilldo");
   }
   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);
   }
}

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

