/*****************************************************************************/
/*
                               AuthACME.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.


The Authentication and Credentials Management Extensions (ACME) subsystem
provides authentication and persona-based credential services.  Applications
use these services to enforce authentication policies defined by ACME agents
running in the context of the ACME_SERVER process.

This module will form the basis of an authentication capability using the
$ACM() system service and the "Authentication and Credential Management
Extensions" server.

It is not available on VAX and is only for VMS later than V7.3.

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


WHY ACME?
---------
It offers a vendor maintained authentication service that, in part, parallels
that provided by the WASD SYSUAF authenication facility.  Of course, the
"vendor maintained" is the important bit!  It should be more robust and
less-likely to get (or be) broken than the WASD equivalent.

So, for applicable platforms it can be enabled using [AuthSysUafUseACME]
directive to do the SYSUAF authentication part transparently.  No more
$GETAUI() and then checking password expiry and creating password hashes and
comparing them, etc., etc.  It's all handled by the one SYS$ACM() call.  Does
this mean the historical AUTHVMS.C contortions can be dispensed with?  Well,
no, of course not.  For non-supported platforms it still must be maintained, as
well as WASD's use of some portions of the SYSUAF account information for
further controlling access (e.g. privileged accounts, rights identifiers).

Any down sides?  Well it appears as if the authentication failure information
returned by SYS$ACM() is quite chunky, basically one %ACME-E-AUTHFAIL, so you
lose the explanatory information available with AuthVmsVerifyUser().

The WASD SYSUAF password change functions available via HTADMIN.C and AUTHVMS.C
also get a gee-up by using the ACME service.  The password change SYS$ACM()
will invoke the site's full password policy (much better than the half-assed
WASD implementation) and so potentially provide dictionary and password history
functionality, etc., for web-based password change.

Other reasons for using ACME ... well it can authenticate from sources other
than the SYSUAF and supply a mapped VMS username as a result.  At the time of
writing the only other HP-supported "domain of interpretation" is the "MSV1_0"
Microsoft Lan Manager domain-based authentication.  There may be more in the
future, including third-party and locally developed ACME plug-ins.  So WASD
having this now provides for future authentication developments as well.


VERSION HISTORY
---------------
16-MAR-2004  MGD  initial (new with WASD v8.5)
*/
/*****************************************************************************/

#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

#ifndef WASD_ACME
#  define WASD_ACME 0
#endif

/**************************************/
#if WASD_ACME  /* ACME authentication */
/**************************************/

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <utcblkdef.h>
#include <acmedef.h>
#include <acmemsgdef.h>
#include <descrip.h>
#include <iledef.h>
#define ILE3 ile3  /* because of the __VMS_VER */
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>

#include <uaidef.h>
/* not defined in VAX C 3.2 <uaidef.h> */
#define UAI$M_RESTRICTED 0x8
#define UAI$C_PURDY_S 3

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

#define WASD_MODULE "AUTHACME"

#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

/******************/
/* global storage */
/******************/

/* this image is linked with ACME capabilities */
BOOL  AuthAcmeLinked = true;

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

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

extern BOOL  AuthConfigACME,
             AuthSysUafEnabled;

extern int  EfnWait,
            EfnNoWait;

extern char  ErrorSanityCheck[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Verify the request username/password using $ACM() services.
Return the results via AST AuthAcmeVerifyUserAst().
*/ 

int AuthAcmeVerifyUser (REQUEST_STRUCT* rqptr)

{
   static unsigned long  AcmeLogonType = ACME$K_NETWORK;

   int  status;
   char  *cptr, *sptr, *zptr;
   AUTH_ACME  *acmeptr;
   ILE3  *itmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthAcmeVerifyUser() !&Z !&Z",
                 rqptr->rqAuth.RealmPtr, rqptr->RemoteUser);

   if (!AuthConfigACME)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   acmeptr = rqptr->rqAuth.AcmeDataPtr =
      (AUTH_ACME*)VmGetHeap (rqptr, sizeof(AUTH_ACME));

   itmptr = &acmeptr->AcmItemList;

   /* if configuration [AuthSysUafUseACME] or realm name contains "VMS" */
   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS\0' ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS-' ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS_')
   {
      cptr = "VMS";
      rqptr->rqAuth.SysUafAuthenticated = true;
      /* flag that case-less username and password checks were performed */
      rqptr->rqAuth.CaseLess = true;
   }
   else
      cptr = rqptr->rqAuth.RealmPtr;

   zptr = (sptr = acmeptr->TargetDoiName) + sizeof(acmeptr->TargetDoiName)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   acmeptr->TargetDoiNameLength = sptr - acmeptr->TargetDoiName;

   itmptr->ile3$w_code = ACME$_TARGET_DOI_NAME;
   itmptr->ile3$w_length = acmeptr->TargetDoiNameLength;
   itmptr->ile3$ps_bufaddr = acmeptr->TargetDoiName;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_LOGON_TYPE;
   itmptr->ile3$w_length = sizeof(AcmeLogonType);
   itmptr->ile3$ps_bufaddr = &AcmeLogonType;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_PRINCIPAL_NAME_IN;
   itmptr->ile3$w_length = rqptr->RemoteUserLength;
   itmptr->ile3$ps_bufaddr = rqptr->RemoteUser;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_PASSWORD_1;
   itmptr->ile3$w_length = strlen(rqptr->RemoteUserPassword);
   itmptr->ile3$ps_bufaddr = rqptr->RemoteUserPassword;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_MAPPED_VMS_USERNAME;
   itmptr->ile3$w_length = sizeof(acmeptr->MappedVmsUserName);
   itmptr->ile3$ps_bufaddr = acmeptr->MappedVmsUserName;
   itmptr->ile3$ps_retlen_addr = &acmeptr->MappedVmsUserNameLength;
   itmptr++;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      itmptr->ile3$w_code = ACME$_MAPPING_ACME_NAME;
      itmptr->ile3$w_length = sizeof(acmeptr->MappingAcmeName);
      itmptr->ile3$ps_bufaddr = acmeptr->MappingAcmeName;
      itmptr->ile3$ps_retlen_addr = &acmeptr->MappingAcmeNameLength;
      itmptr++;
   }

   memset (itmptr, 0, sizeof(ILE3));

   status = sys$acm (EfnNoWait,
                     ACME$_FC_AUTHENTICATE_PRINCIPAL,
                     0,
                     &acmeptr->AcmItemList,
                     &acmeptr->AcmIOsb,
                     &AuthAcmeVerifyUserAst,
                     rqptr);

   if (VMSnok (status))
   {
      /* an error with sys$acm(), fudge status and queue AST manually */
      acmeptr->AcmIOsb[0] = acmeptr->AcmIOsb[1] = status;
      SysDclAst (AuthAcmeVerifyUserAst, rqptr);
   }

   /* function completes asynchronously! */
   rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer;
   rqptr->rqAuth.FinalStatus = AUTH_PENDING;
   return (AUTH_PENDING);
}

/*****************************************************************************/
/*
AST delivered from the ACME service.  Check the status and adjust to a WASD
AUTH value if necessary.  If a SYSUAF-equivalent authentication then also call
AuthVmsVerifyUser() to check against other WASD restrictions (e.g. rights
identifier authentication control), and if then still authenticated also check
if the primary password has expired and if it has the redirect to a configured
'change password' URL or deny access.
*/ 

AuthAcmeVerifyUserAst (REQUEST_STRUCT* rqptr)

{
   int  status;
   char  *cptr;
   AUTH_ACME  *acmeptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthAcmeVerifyUserAst()");

   acmeptr = rqptr->rqAuth.AcmeDataPtr;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      for (cptr = acmeptr->MappingAcmeName; *cptr && !isspace(*cptr); cptr++);
      *cptr = '\0';
      acmeptr->MappingAcmeNameLength = cptr - acmeptr->MappingAcmeName;

      if (VMSok(acmeptr->AcmIOsb[0]) &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" agent:\"!AZ\" mapped:\"!AZ\" !&S !-%!&M",
                    acmeptr->TargetDoiName,
                    acmeptr->MappingAcmeName,
                    acmeptr->MappedVmsUserName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      if (!acmeptr->AcmIOsb[3] &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" !&S !-%!&M",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" sts:!&S sec:!&S id:!UL acme:!&S",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0], acmeptr->AcmIOsb[1],
                    acmeptr->AcmIOsb[2], acmeptr->AcmIOsb[3],
                    acmeptr->MappedVmsUserName);
         WatchDataFormatted ("%!&M\n%!&M\n",
                             acmeptr->AcmIOsb[0],
                             acmeptr->AcmIOsb[1]);
      }
   }

   status = acmeptr->AcmIOsb[0];

   /* convert an ACME auth failure into a retryable server auth failure */
   if (status == ACME$_AUTHFAILURE) status = AUTH_DENIED_BY_LOGIN;

   /* if authentication OK and it effectively was from the SYSUAF */
   if (VMSok (status) && rqptr->rqAuth.SysUafAuthenticated)
   {
      /* check there are no further (WASD-specific) restrictions */
      if (VMSok (status = AuthVmsGetUai (rqptr, acmeptr->MappedVmsUserName)))
      {
         if (VMSok (status = AuthVmsVerifyUser (rqptr)))
         {
            /* authenticated ... user can do anything (the path allows!) */
            rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
         }
      }

      /* if authenticated */
      if (VMSok (status))
      {
         /* assumes flag has been set by a prior call to AuthVmsVerifyUser() */
         if (rqptr->rqAuth.SysUafPwdExpired &&
             !strsame (rqptr->rqHeader.RequestUriPtr,
                       INTERNAL_PASSWORD_CHANGE,
                       sizeof(INTERNAL_PASSWORD_CHANGE)-1))
         {
            /* password has expired (and this is not the 'change' path) */
            if (rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr)
            {
               rqptr->rqResponse.LocationPtr =
                  rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr;
               status = AUTH_DENIED_BY_REDIRECT;
            }
            else
            if (Config.cfAuth.SysUafPwdExpUrl[0])
            {
               rqptr->rqResponse.LocationPtr = Config.cfAuth.SysUafPwdExpUrl;
               status = AUTH_DENIED_BY_REDIRECT;
            }
            else
               status = AUTH_DENIED_BY_LOGIN;
         }
      }

      if (VMSok (status))
      {
         /* buffer the original remote user in the authorization structure */
         strcpy (rqptr->rqAuth.RemoteUser, rqptr->RemoteUser);
         rqptr->rqAuth.RemoteUserLength = rqptr->RemoteUserLength;

         /* replace with the ACME mapped username */
         strcpy (rqptr->RemoteUser, acmeptr->MappedVmsUserName);
         rqptr->RemoteUserLength = strlen(acmeptr->MappedVmsUserName);
      }
   }

   rqptr->rqAuth.FinalStatus = status;

   SysDclAst (AuthorizeRealmCheck, rqptr);
}

/*****************************************************************************/
/*
Change the specified username's ('rqptr->RemoteUser') password in the specified
ACME domain  ('rqptr->rqAuth.RealmPtr').  It assumes the calling routine
HTAdminChangePassword() has performed required preliminary sanity checks.  This
function is just to perform the change - ASYNCHRONOUSLY!
*/ 

AuthAcmeChangePassword
(
REQUEST_STRUCT* rqptr,
char *PasswordCurrent,
char *PasswordNew
)
{
   /* doesn't work using ACME$K_NETWORK (which is sort of understandable) */
   static unsigned long  AcmeLogonType = ACME$K_LOCAL;
   static unsigned long  NewPasswordFlags = ACMEPWDFLG$M_PASSWORD_1;

   int  len, status;
   char  *cptr, *sptr, *zptr;
   AUTH_ACME  *acmeptr;
   ILE3  *itmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthAcmeChangePassword() !&Z !&Z",
                 rqptr->rqAuth.RealmPtr, rqptr->RemoteUser);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "CHANGE ACME password");

   if (!AuthConfigACME)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   acmeptr = rqptr->rqAuth.AcmeDataPtr =
      (AUTH_ACME*)VmGetHeap (rqptr, sizeof(AUTH_ACME));

   itmptr = &acmeptr->AcmItemList;

   /* if configuration [AuthSysUafUseACME] or realm name contains "VMS" */
   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS\0' ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS-' ||
       *(unsigned long*)rqptr->rqAuth.RealmPtr == 'VMS_')
      cptr = "VMS";
   else
      cptr = rqptr->rqAuth.RealmPtr;

   zptr = (sptr = acmeptr->TargetDoiName) + sizeof(acmeptr->TargetDoiName)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   acmeptr->TargetDoiNameLength = sptr - acmeptr->TargetDoiName;

   itmptr->ile3$w_code = ACME$_TARGET_DOI_NAME;
   itmptr->ile3$w_length = acmeptr->TargetDoiNameLength;
   itmptr->ile3$ps_bufaddr = acmeptr->TargetDoiName;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_LOGON_TYPE;
   itmptr->ile3$w_length = sizeof(AcmeLogonType);
   itmptr->ile3$ps_bufaddr = &AcmeLogonType;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_PRINCIPAL_NAME_IN;
   itmptr->ile3$w_length = rqptr->RemoteUserLength;
   itmptr->ile3$ps_bufaddr = rqptr->RemoteUser;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   len = strlen(PasswordCurrent);
   cptr = VmGetHeap (rqptr, len+1);
   strcpy (cptr, PasswordCurrent);

   itmptr->ile3$w_code = ACME$_PASSWORD_1;
   itmptr->ile3$w_length = len;
   itmptr->ile3$ps_bufaddr = cptr;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   itmptr->ile3$w_code = ACME$_NEW_PASSWORD_FLAGS;
   itmptr->ile3$w_length = sizeof(NewPasswordFlags);
   itmptr->ile3$ps_bufaddr = &NewPasswordFlags;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   len = strlen(PasswordNew);
   cptr = VmGetHeap (rqptr, len+1);
   strcpy (cptr, PasswordNew);

   itmptr->ile3$w_code = ACME$_NEW_PASSWORD_1;
   itmptr->ile3$w_length = len;
   itmptr->ile3$ps_bufaddr = cptr;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   memset (itmptr, 0, sizeof(ILE3));

   status = sys$acm (EfnNoWait,
                     ACME$_FC_CHANGE_PASSWORD,
                     0,
                     &acmeptr->AcmItemList,
                     &acmeptr->AcmIOsb,
                     &AuthAcmeChangePasswordAst,
                     rqptr);

   if (VMSnok (status))
   {
      /* an error with sys$acm(), fudge status and queue AST manually */
      acmeptr->AcmIOsb[0] = acmeptr->AcmIOsb[1] = status;
      SysDclAst (AuthAcmeChangePasswordAst, rqptr);
   }
}

/*****************************************************************************/
/*
AST delivered from the ACME service.
*/ 

AuthAcmeChangePasswordAst (REQUEST_STRUCT* rqptr)

{
   int  status;
   char  *cptr;
   AUTH_ACME  *acmeptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthAcmeChangePasswordAst()");

   acmeptr = rqptr->rqAuth.AcmeDataPtr;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      for (cptr = acmeptr->MappingAcmeName; *cptr && !isspace(*cptr); cptr++);
      *cptr = '\0';
      acmeptr->MappingAcmeNameLength = cptr - acmeptr->MappingAcmeName;

      if (VMSok(acmeptr->AcmIOsb[0]) &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" !&S !-%!&M",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      if (!acmeptr->AcmIOsb[3] &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" !&S !-%!&M",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      {
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
"ACME doi:\"!AZ\" sts:!&S sec:!&S id:!UL acme:!&S",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0], acmeptr->AcmIOsb[1],
                    acmeptr->AcmIOsb[2], acmeptr->AcmIOsb[3],
                    acmeptr->MappedVmsUserName);
         WatchDataFormatted ("%!&M\n%!&M\n",
                             acmeptr->AcmIOsb[0],
                             acmeptr->AcmIOsb[1]);
      }
   }

   status = acmeptr->AcmIOsb[0];

   if (VMSnok (status)) 
   {
      rqptr->rqResponse.HttpStatus = 403;
      cptr = VmGetHeap (rqptr, 256);
      WriteFao (cptr, 255, NULL, "!&m.", status);
      *cptr = toupper(*cptr);
      ErrorGeneral (rqptr, cptr, FI_NOLI);
   }

   HTAdminChangePasswordEnd (rqptr);
}

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

/************************/
#else  /* #if WASD_ACME */
/************************/

#include "wasd.h"

#define WASD_MODULE "AUTHACME"

extern char  ErrorSanityCheck[];

/* this image is linked without ACME capabilities */
BOOL AuthAcmeLinked = 0;

/* just for linkage and sanity checking */
int AuthAcmeVerifyUser (REQUEST_STRUCT* rqptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   /* keep the compile quiet using a return statement */
   return (SS$_BUGCHECK);
}

AuthAcmeVerifyUserAst (REQUEST_STRUCT* rqptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

AuthAcmeChangePassword
(
REQUEST_STRUCT *rqptr,
char *PasswordCurrent,
char *PasswordNew
)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

AuthAcmeChangePasswordAst (REQUEST_STRUCT* rqptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

/*************************/
#endif  /* #if WASD_ACME */
/*************************/

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

