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

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 authentication 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 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
---------------
07-SEP-2024  MGD  extend skeleton-key functionality
10-OCT-2009  MGD  AuthAcmeVerifyUser() requires SECURITY privilege to
                  allow ACME$M_NOAUTHORIZATION for authentication-only
                  when using WASD_NIL_ACCESS identifier,
                  AuthAcmeVerifyUser() can now use [AuthSYSUAFlogonType] and/or
                  an optional authorization rule parameter 'param="logon=.."'
                  to specify the login mode (default is still NETWORK) 
29-APR-2009  MGD  DOI name of '*' indicates use the default of
                  ACME$LATEST_ENABLED_AGENT_LIST rather than the specified DOI
                  (authentication realm is set to the DOI authentication realm)
24-NOV-2007  MGD  force ACME on VMS V7.3 and later
31-JUL-2005  MGD  add remote IP address to refine intrusion data and
                  reduce possibility of DOS attack on particular usernames,
                  bugfix; AuthAcmeVerifyUser() ACME$_LOGON_TYPE requires
                  IMPERSONATE (DETACH) privilege for VMS V7.3-1 and earlier
16-MAR-2004  MGD  initial (new with WASD v8.5)
*/
/*****************************************************************************/

#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

#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>

/* 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,
             AuthNilAccessExists,
             AuthSysUafEnabled;

extern int  EfnWait,
            EfnNoWait;

extern unsigned long  DetachMask[],
                      SecurityMask[];

extern char  ErrorSanityCheck[];

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

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

int AuthAcmeVerifyUser (REQUEST_STRUCT* rqptr)

{
   int  status,
        SetPrvStatus,
        UserNameLength;
   unsigned long  AcmeLogonType,
                  FuncCode;
   unsigned long  PrivMask [2];
   char  *cptr, *sptr, *zptr;
   char  UserNameBuffer [256];
   AUTH_ACME  *acmeptr;
   ILE3  *itmptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthAcmeVerifyUser() !&Z !&Z",
                 rqptr->rqAuth.RealmPtr, rqptr->RemoteUser);

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

   rqptr->rqAuth.SysUafAuthenticated = true;

   if (!(cptr = rqptr->rqAuth.PathParameterPtr)) cptr = "";
   while (*cptr && (to_lower(*cptr) != 'l' || !strsame(cptr,"logon=",6))) cptr++;
   if (*cptr)
   {
      /* same set of parameters as used by AuthVmsGetUai() */
      if (strsame (cptr, "logon=NETWORK", 13))
         AcmeLogonType = ACME$K_NETWORK;
      else
      if (strsame (cptr, "logon=BATCH", 11))
         AcmeLogonType = ACME$K_BATCH;
      else
      if (strsame (cptr, "logon=LOCAL", 11))
         AcmeLogonType = ACME$K_LOCAL;
      else
      if (strsame (cptr, "logon=DIALUP", 12))
         AcmeLogonType = ACME$K_DIALUP;
      else
      if (strsame (cptr, "logon=REMOTE", 12))
         AcmeLogonType = ACME$K_REMOTE;
      else
      {
         AcmeLogonType = 0;
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                       "unknown ACME$_LOGON_TYPE \"!AZ\"", cptr);
      }
   }
   else
   if (Config.cfAuth.SysUafLogonType)
      AcmeLogonType = Config.cfAuth.SysUafLogonType;
   else
      AcmeLogonType = ACME$K_NETWORK;

   rqptr->rqAuth.SysUafLogonType = AcmeLogonType;

   FuncCode = ACME$_FC_AUTHENTICATE_PRINCIPAL;
   PrivMask[0] = DetachMask[0];
   PrivMask[1] = DetachMask[1];

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

   itmptr = &acmeptr->AcmItemList;

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS\0") ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS-") ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS_"))
   {
      cptr = "VMS";
      /* flag that case-less username and password checks were performed */
      rqptr->rqAuth.CaseLess = true;
      FuncCode = ACME$_FC_AUTHENTICATE_PRINCIPAL;
      if (AuthNilAccessExists)
      {
         /* don't authorize access, just authenticate credentials */
         FuncCode |= ACME$M_NOAUTHORIZATION;
         PrivMask[0] |= SecurityMask[0];
         PrivMask[1] |= SecurityMask[1];
      }
   }
   else
      cptr = rqptr->rqAuth.RealmPtr;

   if (*cptr == '*')
   {
      /* use the default DOI(s) ACME$LATEST_ENABLED_AGENT_LIST? */
      itmptr->ile3$w_code = ACME$_AUTHENTICATING_DOI_NAME;
      itmptr->ile3$w_length = sizeof(acmeptr->AuthDoiName)-1;
      itmptr->ile3$ps_bufaddr = acmeptr->AuthDoiName;
      itmptr->ile3$ps_retlen_addr = &acmeptr->AuthDoiNameLength;
      itmptr++;

      /* just for WATCH purposes */
      acmeptr->TargetDoiName[0] = '*';
   }
   else
   {
      /* specify the DOI */
      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++;
   }

   /* requires IMPERSONATE (DETACH) privilege on VMS V7.3-1 or earlier */
   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++;

   /* requires IMPERSONATE (DETACH) privilege */
   itmptr->ile3$w_code = ACME$_REMOTE_HOST_NAME;
   itmptr->ile3$w_length = strlen(&rqptr->ClientPtr->IpAddressString);
   itmptr->ile3$ps_bufaddr = &rqptr->ClientPtr->IpAddressString;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   /* create a composite string containing the user name */
   zptr = (sptr = UserNameBuffer) + sizeof(UserNameBuffer)-1;
   for (cptr = "WASD:"; *cptr; *sptr++ = *cptr++);
   for (cptr = rqptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   UserNameLength = sptr - UserNameBuffer;
   cptr = VmGetHeap (rqptr, UserNameLength+1);
   memcpy (cptr, UserNameBuffer, UserNameLength+1);

   /* requires IMPERSONATE (DETACH) privilege */
   itmptr->ile3$w_code = ACME$_REMOTE_USERNAME;
   itmptr->ile3$w_length = UserNameLength;
   itmptr->ile3$ps_bufaddr = cptr;
   itmptr->ile3$ps_retlen_addr = 0;
   itmptr++;

   if (rqptr->rqAuth.SkelKeyAuthenticated)
   {
      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "SKELKEY proxy authenticated");
   }
   else
   {
      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_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));

   /* provide required privilege(s) */
   if (VMSnok (SetPrvStatus = sys$setprv (1, &PrivMask, 0, 0)))
      ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

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

   if (VMSnok (SetPrvStatus = sys$setprv (0, &PrivMask, 0, 0)))
      ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

   if (AuthNilAccessExists)
      if (status == SS$_NOPRIV)
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                       "!AZ requires INSTALL with SECURITY privilege",
                       AUTH_NIL_ACCESS_VMS_ID);

   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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthAcmeVerifyUserAst()");

   acmeptr = rqptr->rqAuth.AcmeDataPtr;

   if (WATCHING (rqptr, 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 (WATCHITM(rqptr), WATCH_AUTH,
"ACME doi:\"!AZ\" agent:\"!AZ\" mapped:\"!AZ\" logon:!AZ !&S",
                    acmeptr->TargetDoiName,
                    acmeptr->MappingAcmeName,
                    acmeptr->MappedVmsUserName,
                    AuthSysUafLogonType(rqptr->rqAuth.SysUafLogonType),
                    acmeptr->AcmIOsb[0]);
      }
      else
      if (!acmeptr->AcmIOsb[3] &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
"ACME doi:\"!AZ\" logon:!AZ !&S",
                    acmeptr->TargetDoiName,
                    AuthSysUafLogonType(rqptr->rqAuth.SysUafLogonType),
                    acmeptr->AcmIOsb[0]);
      }
      else
      {
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
"ACME doi:\"!AZ\" logon:!AZ sts:!&S sec:!&S id:!UL acme:!&S",
                    acmeptr->TargetDoiName,
                    AuthSysUafLogonType(rqptr->rqAuth.SysUafLogonType),
                    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 (VMSok (status) && strsame (acmeptr->TargetDoiName, "VMS", -1))
      rqptr->rqAuth.SysUafAuthenticated = true;

   /* if authentication OK and it effectively was from the SYSUAF */
   if (VMSok (status) && rqptr->rqAuth.SysUafAuthenticated)
   {
      if (rqptr->rqAuth.RealmPtr[0] == '*')
      {
         /* use the authenticating DOI as the realm */
         rqptr->rqAuth.RealmPtr = acmeptr->AuthDoiName;
         rqptr->rqAuth.RealmLength = acmeptr->AuthDoiNameLength;
         rqptr->rqAuth.RealmPtr[rqptr->rqAuth.RealmLength] = '\0';
      }

      /* 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))
      {
         if (rqptr->rqAuth.SysUafPwdExpired)
         {
            /* password has expired */
            if (!strsame (rqptr->rqHeader.RequestUriPtr,
                          INTERNAL_PASSWORD_CHANGE,
                          sizeof(INTERNAL_PASSWORD_CHANGE)-1))
            {
               /* and not in the process of changing it */
               if (rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr)
                  cptr = rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr;
               else
               if (Config.cfAuth.SysUafPwdExpUrl[0])
                  cptr = Config.cfAuth.SysUafPwdExpUrl;
               else
                  cptr = NULL;

               if (cptr)
               {
                  /* expired password URL is configured */
                  if (!strsame (rqptr->rqHeader.RequestUriPtr, cptr, -1))
                  {
                     /* request URI doesn't match it though, so redirect */
                     ResponseLocation (rqptr, cptr, -1);
                     status = AUTH_DENIED_BY_REDIRECT;
                  }
               }
               else
               {
                  /* is not configured */
                  status = AUTH_DENIED_BY_LOGIN;
               }
            }
         }
      }

      if (VMSok (status))
      {
         /* buffer the original remote user (the browser-supplied user id) */
         strzcpy (rqptr->rqAuth.RemoteUser, rqptr->RemoteUser,
                  sizeof(rqptr->rqAuth.RemoteUser));
         rqptr->rqAuth.RemoteUserLength = rqptr->RemoteUserLength;

         /* replace with the ACME mapped username */
         strzcpy (rqptr->RemoteUser, acmeptr->MappedVmsUserName,
                  sizeof(rqptr->RemoteUser));
         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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthAcmeChangePassword() !&Z !&Z",
                 rqptr->rqAuth.RealmPtr, rqptr->RemoteUser);

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), 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 (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID ||
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS\0") ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS-") ||
       MATCH4 (rqptr->rqAuth.RealmPtr, "VMS_"))
      cptr = "VMS";
   else
      cptr = rqptr->rqAuth.RealmPtr;

   if (*cptr == '*')
   {
      /* use the default DOI(s) ACME$LATEST_ENABLED_AGENT_LIST? */
      itmptr->ile3$w_code = ACME$_AUTHENTICATING_DOI_NAME;
      itmptr->ile3$w_length = sizeof(acmeptr->AuthDoiName)-1;
      itmptr->ile3$ps_bufaddr = acmeptr->AuthDoiName;
      itmptr->ile3$ps_retlen_addr = &acmeptr->AuthDoiNameLength;
      itmptr++;

      /* just for WATCH purposes */
      acmeptr->TargetDoiName[0] = '*';
   }
   else
   {
      /* specify the DOI */
      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);
   strzcpy (cptr, PasswordCurrent, len+1);

   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);
   strzcpy (cptr, PasswordNew, len+1);

   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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthAcmeChangePasswordAst()");

   acmeptr = rqptr->rqAuth.AcmeDataPtr;

   if (WATCHING (rqptr, 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 (WATCHITM(rqptr), WATCH_AUTH,
"ACME doi:\"!AZ\" !&S",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      if (!acmeptr->AcmIOsb[3] &&
          acmeptr->AcmIOsb[0] == acmeptr->AcmIOsb[1])
      {
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
"ACME doi:\"!AZ\" !&S",
                    acmeptr->TargetDoiName,
                    acmeptr->AcmIOsb[0]);
      }
      else
      {
         WatchThis (WATCHITM(rqptr), 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 (VMSok (status)) 
   {
      if (rqptr->rqAuth.RealmPtr[0] == '*')
      {
         /* use the authenticating DOI as the realm */
         rqptr->rqAuth.RealmPtr = acmeptr->AuthDoiName;
         rqptr->rqAuth.RealmLength = acmeptr->AuthDoiNameLength;
         rqptr->rqAuth.RealmPtr[rqptr->rqAuth.RealmLength] = '\0';
      }
   }
   else
   {
      rqptr->rqResponse.HttpStatus = 403;
      cptr = VmGetHeap (rqptr, 256);
      FaoToBuffer (cptr, 255, NULL, "!&m.", status);
      *cptr = to_upper(*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 */
/*************************/

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

