/*****************************************************************************/
/*
                               AuthHTL.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 the authentication of
case-insensitive usernames via simple lists of names (and optional
case-insensitive passwords and user detail).

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


VERSION HISTORY
---------------
26-AUG-2003  MGD  service directory located authorization databases
12-AUG-2003  MGD  allow the database directory location to be specified using
                  an authorization rule 'param="/directory=device:[directory]"'
27-APR-2002  MGD  use sys$setprv()
04-AUG-2001  MGD  support module WATCHing
05-FEB-2000  MGD  change HTL database type from ".HTL" to ".$HTL"
                  (keep the nomenclature in line with ".$HTA", see AUTHHTA.C)
01-JAN-2000  MGD  no significant modifications for ODS-5 (no NAM block)
28-AUG-1999  MGD  unbundled from AUTH.C for v6.1
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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 <rms.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "AUTHHTL"

#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

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

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern unsigned long  SysPrvMask[];
extern char  ErrorSanityCheck[];
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

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

int AuthReadSimpleList
(
REQUEST_STRUCT* rqptr,
char *DatabaseName,
BOOL AuthenticatePassword
)
{
   int  status,
        AuthFileNameLength;
   char  *cptr, *sptr, *zptr,
         *cptrBuffer;
   char  AuthFileName [256],
         Line [1024];
   struct FAB  AuthFileFab;
   struct RAB  AuthFileRab;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthReadSimpleList() !&Z !&Z !&B",
                 DatabaseName, rqptr->RemoteUser, AuthenticatePassword);

   /* flag that case-less username and password checks were performed */
   rqptr->rqAuth.CaseLess = true;

   /* must supply a user name and database name */
   if (!rqptr->RemoteUser[0] || !DatabaseName[0])
      return (AUTH_DENIED_BY_LOGIN);

   if (rqptr->rqAuth.PathParameterPtr &&
       rqptr->rqAuth.PathParameterPtr[0])
   {
      cptr = rqptr->rqAuth.PathParameterPtr;
      if (strsame (cptr, "/DIRECTORY=", 11)) cptr += 11;
   }
   else
   if (rqptr->rqAuth.DirectoryPtr)
     cptr = rqptr->rqAuth.DirectoryPtr;
   else
   if (rqptr->ConfigDirectory[0])
      cptr = rqptr->ConfigDirectory;
   else
      cptr = HTA_DIRECTORY;

   /* just a safeguard against the service directory not being configured */
   if (!*cptr) cptr = AUTH_DIR_NOT_CONFIGURED;

   zptr = (sptr = AuthFileName) + sizeof(AuthFileName)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   for (cptr = DatabaseName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = HTL_FILE_TYPE; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   AuthFileNameLength = sptr - AuthFileName;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH, "FILE !AZ", AuthFileName);

   AuthFileFab = cc$rms_fab;
   AuthFileFab.fab$b_fac = FAB$M_GET;
   AuthFileFab.fab$l_fna = AuthFileName;  
   AuthFileFab.fab$b_fns = AuthFileNameLength;
   AuthFileFab.fab$b_shr = FAB$M_SHRGET;

   /* turn on SYSPRV to allow access to authentication database file */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$open (&AuthFileFab, 0, 0);

   sys$setprv (0, &SysPrvMask, 0, 0);

   /* status from sys$open() */
   if (VMSnok (status))
   {
      rqptr->rqResponse.HttpStatus = 500;
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE);
      rqptr->rqResponse.ErrorOtherTextPtr = DatabaseName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   AuthFileRab = cc$rms_rab;
   AuthFileRab.rab$l_fab = &AuthFileFab;
   AuthFileRab.rab$b_mbf = 2;
   AuthFileRab.rab$l_rop = RAB$M_RAH;

   if (VMSnok (status = sys$connect (&AuthFileRab, 0, 0)))
   {
      sys$close (&AuthFileFab, 0, 0);
      rqptr->rqResponse.HttpStatus = 500;
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE);
      rqptr->rqResponse.ErrorOtherTextPtr = DatabaseName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   /*****************/
   /* locate record */
   /*****************/

   AuthFileRab.rab$l_ubf = Line;
   AuthFileRab.rab$w_usz = sizeof(Line)-1;

   while (VMSok (status = sys$get (&AuthFileRab, 0, 0)))
   {
      AuthFileRab.rab$l_rbf[AuthFileRab.rab$w_rsz] = '\0';
      if (AuthFileRab.rab$w_rsz)
      {
         if (AuthFileRab.rab$l_ubf[AuthFileRab.rab$w_rsz-1] == '\\')
         {
            /* directive is continued on next line */
            AuthFileRab.rab$l_ubf[AuthFileRab.rab$w_rsz-1] = ' ';
            AuthFileRab.rab$l_ubf += AuthFileRab.rab$w_rsz;
            AuthFileRab.rab$w_usz -= AuthFileRab.rab$w_rsz;
            continue;
         }
      }
      for (cptr = Line; *cptr && ISLWS(*cptr); cptr++)
      if (*cptr == '#' || *cptr == '!') continue;

      sptr = rqptr->RemoteUser;
      while (*cptr && !ISLWS(*cptr) && *cptr != '=' && *sptr &&
             tolower(*cptr) == tolower(*sptr))
         { cptr++; sptr++; }
      if ((*cptr && !ISLWS(*cptr) && *cptr != '=') || *sptr) continue;
      break;
   }
   if (Debug) fprintf (stdout, "sys$get() %%X%08.08X\n", status);

   sys$close (&AuthFileFab);

   if (status == RMS$_EOF)
   {
      /* user record not found */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL !AZ!AZ username", DatabaseName,
                    AuthSourceString (DatabaseName, AUTH_SOURCE_LIST));

      if (AuthenticatePassword)
         return (AUTH_DENIED_BY_LOGIN);
      else
         return (AUTH_DENIED_BY_GROUP);
   }

   /* if just checking if the user name is an entry in the list file */
   if (!AuthenticatePassword) return (SS$_NORMAL);

   sptr = cptr;
   if (*sptr == '=') while (*sptr && !ISLWS(*sptr)) sptr++;
   while (*sptr && ISLWS(*sptr)) sptr++;
   if (*sptr)
   {
      cptrBuffer = cptr;
      for (cptr = sptr; *cptr; cptr++);
      if (cptr > sptr) cptr--;
      while (cptr > sptr && ISLWS(*cptr)) cptr--;
      if (!ISLWS(*cptr)) cptr++;
      *cptr = '\0';
      rqptr->rqAuth.UserDetailsLength = cptr - sptr;
      rqptr->rqAuth.UserDetailsPtr = cptr =
         VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
      strcpy (cptr, sptr);
      cptr = cptrBuffer;
   }

   /* if just checking if the user name is an entry in the list file */
   if (!AuthenticatePassword) return (status);

   /* simple, clear-text password comparison */
   if (*cptr != '=')
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL !AZ!AZ no password", DatabaseName,
                    AuthSourceString (DatabaseName, AUTH_SOURCE_LIST));
      return (AUTH_DENIED_BY_LOGIN);
   }
   while (*cptr && *cptr == '=') cptr++;
   sptr = rqptr->RemoteUserPassword;
   while (*cptr && !ISLWS(*cptr) && *sptr && tolower(*cptr) == tolower(*sptr))
      { cptr++; sptr++; }
   if ((*cptr && !ISLWS(*cptr)) || *sptr)
   {
      /* passwords do not match */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "FAIL !AZ!AZ password", DatabaseName,
                    AuthSourceString (DatabaseName, AUTH_SOURCE_LIST));
      return (AUTH_DENIED_BY_LOGIN);
   }

   /* passwords match */
   return (SS$_NORMAL);
}

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

