/*****************************************************************************/
/*
                            authagent_LDAP_2.c

      *** THIS VERSION USES IT'S OWN CODE TO DO THE AUTHENTICATION ***

LDAP authentication agent CGIplus script, using the callout mechanism.

Code adapted from the OpenVMS Utility Routines Manual, Sample LDAP API Code.

Uses the integrated LDAP run-time support available with VMS V7.3 and later.

Requires the HP SSL Package to be installed and started.

Attempts to improve performance by remaining connected to the last accessed
LDAP server.  If the server host or port changes between authentication
requests (which is generally unlikely) the agent unbinds from the currently
connected LDAP server before connecting to the new.  If there are any 'hard'
errors while processing (e.g. broken connection) the agent unbinds and retries.

Parameters can be passed in the command-line or the HTTPD$AUTH param=
directive, or a combination of both.  Agent persistence means parameters set
remain set until use of the same parameter (qualifier) again overwriting the
previous value, or until the /RESET qualifier is encountered.  This resets the
values of *all* parameters to the default values.  Hence, basic parameters such
as host and port, and perhaps simple binding parameters, may be established at
the command-line (perhaps using a wrapper DCL procedure) and then each
authorization request could vary that as necessary using HTTPD$AUTH param=
directives.  Care must be exercised that one does not interfere with another.
In general though most LDAP agent parameters can just be passed through from
the authorization rule with each request (see example below).

The filter implements the '%u' and '%d' conversion characters to allow complex
expressions to be built using the userid and realm from the user credentials.

Will verify the 'password' attribute returned either:

1) against the SYSUAF using $GETUAI and $HASH_PASSWORD to hash the password
supplied with the request and compare it to the SUSUAF entry.

2) using the 'userPassword' attribute returned to compute a local digest and
compare that to the digest in the attribute.  Current digests supported:

  a)  {SHA} SHA1 (base-64 encoded)
  b)  {SSHA} salted SHA1 (base-64 encoded)
  c)  {MD5} MD5 (base-64 encoded)
  d)  {SMD5} salted MD5 (base-64 encoded)
  e)  plain-text

If the password verifies the 'uid' is used as the VMS username.

If the entry did not contain a 'uid' and a default username has been configured
(using the /UDEF="<string>" parameter) this is substituted as the authenticated
VMS username.  Note that (for the obvious Catch-22) the password cannot be one
validated using the /SYSUAF facility.


RUN-TIME PARAMETERS
-------------------
These parameters are used to pass run-time information to the authentication
agent from the authorization configuration (see Authentication Configuration
below).

/BASE=<string>           ldap_search_s() 'base' string
/CERT=<file-spec>        client certificate and private key (if files are
                           protected may need to be INSTALLed with SYSPRV)
/DATT=<string>           LDAP attribute containing the user detail for the
                           AUTH_USER variable (defaults to 'displayName')
/DIGEST                  verify the returned username and supplied password
                           by matching the digested password locally
/FILTER=<string>         ldap_search_s() 'filter' string
                           (implements the '%u' and '%d' conversions)
/LOCAL                   verify username through locally supplied function
/PATT=<string>           LDAP attribute name containing (encoded) password
                           (defaults to 'userPassword' attribute)
/PORT=<integer>          optional method for specifying LDAP server port
/RESET                   reset the parameters (in persistent environments
                           parameters can remain across uses)
/SERVER=<host[:port]>    host name and optional port number for LDAP server
/SIMPLE=<dn:passwd>      account and ':'-separated password for simple bind
/SSL[=<integer>]         use LDAPS (deprecated SSL) and optionally specify
                           the SSL protocol (default 1; 20, 23, 30, 31, etc)
/SYSUAF                  verify the returned username and supplied password
                           using $GETUAI and $HASH_PASSWORD
/TLS[=<integer>]         use LDAP TLS (current SSL) and optionally specify
                           the SSL protocol (default 1; 20, 23, 30, 31, etc.)
/UDEF=<string>           default VMS username when entry found and password
                           validated but no uid available
/UATT=<string>           LDAP attribute name containing VMS username
                           (defaults to 'uid' attribute)
/VERSION=<integer>       LDAP protocol version (defaults to 3)


COMMAND-LINE PARAMETERS
-----------------------
In addition to the run-time parameters these are available at the command-line.

/AUTH_PASSWORD=<string>  emulates CGI environment AUTH_PASSWORD variable
/DBUG                    low level debug statements
/DUMP                    output the detail of matching entries
/REMOTE_USER=<string>    emulates CGI environment REMOTE_USER variable
/WATCH                   output WASD server WATCH-able statements


AUTHENTICATION CONFIGURATION
----------------------------
The run-time parameters CAN be passed via a 'param=' argument to the HTTPD$AUTH
configuration for the realm.

  ["Just an LDAP Example"=AUTHAGENT_LDAP=agent]

  /an/example/path/* r+w,param='\
  /HOST="ldap.host.domain"/BASE="dc=domain,DC=au"\
  /FILTER="uid=%u"/SIMPLE="account:password"'


COMMAND-LINE CHECKING
---------------------
Most functionality may be checked from the command-line using (almost) exactly
the same syntax and parameters as used in the authentication (CGIplus) mode. 
This allows the format and syntax of the authentication configuration to be
developed and tested interactively.  In addition, and for general information,
queries not possible (e.g. using wildcards) in the authentication mode may be
used in the command-line mode.

  $ LDAPAGE == "$WASD_EXE:AUTHAGENT_LDAP /WATCH"
  $ LDAPAGE /DUMP /HOST="ldap.fnal.gov" /BASE="o=fnal" /FILTER="cn=*smith*"
  $ LDAPAGE /HOST="ldap.host.domain" /BASE="dc=domain,DC=au" -
            /FILTER="uid=%u" /SIMPLE="account:password" -
            /REMOTE_USER="whatever"


PRIVILEGED IMAGE
----------------
To use the /SYSUAF qualifier (and $GETUAI) for verifying the supplied password
the image must be installed with SYSPRV.

  $ INSTALL REPLACE <directory>:AUTHAGENT_LDAP.EXE /AUTHPRIV=SYSPRV


LOGICAL NAMES
-------------
AUTHAGENT_LDAP$DBUG       same as /DBUG
AUTHAGENT_LDAP$WATCH      same as /WATCH


BUILD DETAILS
-------------
Compile then link:
  $ @BUILD_AUTHAGENT_LDAP
To just link:
  $ @BUILD_AUTHAGENT_LDAP LINK


SPONSOR
-------
This software has been developed under the sponsorship of the University of
Malaga and generously made available to the wider WASD community.  Many thanks.


COPYRIGHT
---------
Copyright (C) 2006-2022 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file 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.


VERSION HISTORY (update SOFTWAREVN as well)
---------------
10-JUN-2021  MGD  v2.0.0, minimal changes for WASD v12... agent requirements
04-MAY-2017  MGD  v1.0.3, upper-case kludge to work around SSL1 link issues
                          (needs a lot more effort to make work with OSSL$)
11-MAY-2007  MGD  v1.0.2, belt-and-braces
16-JUL-2006  MGD  v1.0.1, add /DATT and user details callout
08-JUL-2006  MGD  v1.0.0, initial development
*/

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

#ifndef USE_OPENSSL
#define USE_OPENSSL 1
#endif
#ifdef USE_OPENSSL
#define USING_OPENSSL " (SSL)"
#else
#define USING_OPENSSL " (sans SSL)"
#endif

#define SOFTWAREVN "2.0.0"
#define SOFTWARENM "AUTHAGENT_LDAP"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN USING_OPENSSL
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN USING_OPENSSL
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#include <lib$routines.h>
#include <lnmdef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <starlet.h>
#include <stsdef.h>
#include <uaidef.h>

/* application header files */
#include <ldap.h> 
#include "cgilib.h"

#if USE_OPENSSL

/* OpenSSL header files (0.9.3ff) */
#include "openssl/err.h"
#include "openssl/md5.h"
#include "openssl/X509.h"
#include "openssl/ssl.h"
#include "openssl/bio.h"
#include "openssl/buffer.h"
#include "openssl/crypto.h"

#endif /* USE_OPENSSL */

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define BOOL int
#define TRUE 1
#define FALSE 0

#define DEFAULT_ATTRIBUTE_USERNAME "uid"
#define DEFAULT_ATTRIBUTE_PASSWORD "userPassword"
#define DEFAULT_ATTRIBUTE_DISPLAY "displayName"

#define DEFAULT_SSL_PORT 636

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

char  Utility [] = "AUTHAGENT_LDAP";

BOOL  Debug,
      DebugWatch,
      DoDump,
      LdapSimpleBind,
      VerifyDigest,
      VerifyLocal,
      VerifySYSUAF;

int  CgiPlusUsageCount,
     LdapProtocolVersion = LDAP_VERSION3,
     LdapSSL,
     StartTLS;

unsigned short  LdapPort;

char  *AuthAgentPtr,
      *RemoteUserPtr;

char  CertKeyFileName [128],
      CliRemoteUser [48],
      CliAuthPassword [48],
      LdapBase [128],
      LdapFilter [128],
      LdapHost [128],
      LdapSimpleBindDn [128],
      LdapSimpleBindPasswd [64],
      PasswordAttribute [64] = DEFAULT_ATTRIBUTE_PASSWORD,
      PasswordValue [96],
      PasswordHash [128],
      SoftwareId [96],
      UserDisplayAttribute [64] = DEFAULT_ATTRIBUTE_DISPLAY,
      UserDisplayValue [64],
      UserNameAttribute [64] = DEFAULT_ATTRIBUTE_USERNAME,
      UserNameDefault [64],
      UserNameValue [64],
      VmsUserName [48];

unsigned long  SysPrvMask [2] = { PRV$M_SYSPRV, 0 };

/***********************/
/* function prototypes */
/***********************/

int DecodeBase64 (char*, int, char*, int);
void GetParameters ();
void GetRunTimeParameters (char*, BOOL);
void HashMD5 (char*, int, char*, int, char*);
void HashSHA1 (char*, int, char*, int, char*);
void NeedsPrivilegedAccount ();
BOOL ProcessRequest ();
void RemoveSlash (char*);
char* SysTrnLnm (char*, char*);
BOOL VerifyAgainstSYSUAF (char*, char*);
BOOL VerifyLocalFunction (char*, char*);
BOOL VerifyMD5 (char*, char*);
BOOL VerifySMD5 (char*, char*);
BOOL VerifySHA (char*, char*);
BOOL VerifySSHA (char*, char*);
char* WatchElapsed ();
void WatchThis (int, char*, ...);
int strzcpy (char*, char*, int);
BOOL strsame (char*, char*, int);

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

main ()

{
   BOOL  ok;
   int  status;
   char  *cptr;

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

   sprintf (SoftwareId, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion());

   Debug = (SysTrnLnm ("AUTHAGENT_LDAP$DBUG", NULL) != NULL);
   CgiLibEnvironmentSetDebug (Debug);

   GetParameters ();

   CgiLibEnvironmentInit (0, NULL, FALSE);

   if (!SysTrnLnm ("HTTP$INPUT", NULL))
   {
      /* not in a server context (interactive, command-line testing) */
      NeedsPrivilegedAccount ();
      if (DebugWatch) WatchElapsed ();
      ProcessRequest ();
      if (DebugWatch) WatchThis (__LINE__, "ELAPSED %s", WatchElapsed());
      exit (SS$_NORMAL);
   }

   /* MUST only be executed in a CGIplus environment! */
   if (!CgiLibEnvironmentIsCgiPlus ())
   {
      CgiLibResponseHeader (502, "text/plain");
      fputs ("CGIplus!\n", stdout);
      exit (SS$_NORMAL);
   }

   for (;;)
   {
      /* block waiting for the next request */
      CgiLibVar ("");
      CgiPlusUsageCount++;

      if (cptr = CgiLibVarNull("REQUEST_METHOD"))
         if (!*cptr)
         {
            /* proctored into existance */
            CgiLibResponseHeader (204, "application/proctor");
            CgiLibCgiPlusEOF ();
            continue;
         }

      CgiLibCgiPlusCallout ("!AGENT-BEGIN: %s (%s) usage:%d",
                            SoftwareId, CgiLibEnvironmentVersion(),
                            CgiPlusUsageCount);

      /* provide the server attention "escape" sequence record */
      if (!Debug) CgiLibCgiPlusESC ();

      DebugWatch = (SysTrnLnm ("AUTHAGENT_LDAP$WATCH", NULL) != NULL);

      if (DebugWatch) WatchElapsed ();

      /* ensure this is being invoked by the server */
      if (!(AuthAgentPtr = CgiLibVarNull ("AUTH_AGENT"))) exit (SS$_ABORT);
      if (DebugWatch) WatchThis (__LINE__, "AUTH_AGENT %s", AuthAgentPtr);

      /* belt and braces */
      fprintf (stdout, "100 AUTHAGENT-CALLOUT\n");
      fflush (stdout);

      /* have at least two goes at authenticating the user */
      if (!(ok = ProcessRequest ())) ok = ProcessRequest ();

      if (!ok)
      {
         fprintf (stdout, "500 LDAP authenticator.\n");
         fflush (stdout);
      }

      if (DebugWatch) WatchThis (__LINE__, "ELAPSED %s", WatchElapsed());

      /* provide the "escape" end-of-text sequence record */
      if (!Debug) CgiLibCgiPlusEOT ();

      CgiLibCgiPlusCallout ("!AGENT-END:");

      CgiLibCgiPlusEOF ();

      if (!ok) exit (SS$_NORMAL);
   }
}

/*****************************************************************************/
/*
Main authentication request processing function.
*/

BOOL ProcessRequest ()
       
{
   static BOOL  ServerUnbind;
   static char  ConnectedLdapHost [128];
   static short  ConnectedLdapPort;
   static LDAP  *ld; 

   BOOL  PasswordOK;
   int  idx, rc, status,
        EntryCount; 
   char  *cptr, *sptr, *tptr, *zptr,
         *PasswordPtr;
   char  UserFilter [256],
         UserId [64],
         UserRealm [128];
   LDAPMessage  *resptr, *entptr; 
   char  *attptr, *dnptr;
   BerElement  *berptr; 
   char  **AttValues; 

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

   if (Debug) fprintf (stdout, "ProcessRequest()\n");

   if (DebugWatch) WatchThis (__LINE__, "%s", SoftwareId);

   if (AuthAgentPtr) GetRunTimeParameters (AuthAgentPtr, FALSE);

   if (CgiLibEnvironmentIsCgiPlus())
   {
      RemoteUserPtr = CgiLibVarNull ("REMOTE_USER");
      if (!RemoteUserPtr)
      {
         if (DebugWatch) WatchThis (__LINE__, "REMOTE_USER?");
         return (FALSE); 
      }
   }
   else
      RemoteUserPtr = CliRemoteUser;

   /* ensure no covert expressions sneak in under the radar */
   for (cptr = RemoteUserPtr; *cptr; cptr++)
      if (*cptr == '*' || *cptr == '&' || *cptr == '|' || 
          *cptr == '!' || *cptr == '(' || *cptr == ')' ||
          *cptr == '\\') break;
   if (*cptr)
   {
      if (DebugWatch) WatchThis (__LINE__, "REMOTE_USER wildcard/expression!");
      fprintf (stdout, "401 ambiguous credentials.\n");
      fflush (stdout);
      return (TRUE); 
   }

   /********************/
   /* build the filter */
   /********************/

   zptr = (sptr = UserId) + sizeof(UserId)-1;
   for (cptr = RemoteUserPtr;
        *cptr && *cptr != '@' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   if (*cptr) cptr++;
   zptr = (sptr = UserRealm) + sizeof(UserRealm)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   /* implement '%u' for userid and '%d' for realm */
   zptr = (sptr = UserFilter) + sizeof(UserFilter)-1;
   cptr = LdapFilter;
   while (*cptr && sptr < zptr)
   {
      while (*cptr && *cptr != '%' && sptr < zptr) *sptr++ = *cptr++;
      if (!*cptr) break;
      cptr++;
      if (!*cptr) break;
      if (*cptr == 'u')
      {
         cptr++;
         for (tptr = UserId; *tptr && sptr < zptr; *sptr++ = *tptr++);
      }
      else
      if (*cptr == 'd')
      {
         cptr++;
         for (tptr = UserRealm; *tptr && sptr < zptr; *sptr++ = *tptr++);
      }
      else
         /* unknown conversion character, just ignore it */
         *sptr++ = *cptr++;
   }
   *sptr = '\0';

   /* just override the above if from the command-line and no remote user */
   if (!CgiLibEnvironmentIsCgiPlus() && !CliRemoteUser[0])
      strzcpy (UserFilter, LdapFilter, sizeof(UserFilter));

   if (DebugWatch) WatchThis (__LINE__, "FILTER |%s|", UserFilter);

   /******************************/
   /* connect to the LDAP server */
   /******************************/

   /* check if we need to change any persistent connection */ 
   if ((ConnectedLdapHost[0] && strcmp (LdapHost, ConnectedLdapHost)) ||
       (ConnectedLdapPort && ConnectedLdapPort != LdapPort))
      ServerUnbind = TRUE;

   if (ServerUnbind)
   {
      if (DebugWatch)
         WatchThis (__LINE__, "ldap_unbind() %s:%d",
                    ConnectedLdapHost, ConnectedLdapPort);

      rc = ldap_unbind (ld); 

      if (DebugWatch)
         if (rc != LDAP_SUCCESS)
            WatchThis (__LINE__, "ERROR %d %d %%X%08.08X %s",
                       rc, errno, vaxc$errno, ldap_err2string(rc)); 

      ServerUnbind = FALSE;
      ConnectedLdapHost[0] = '\0';
      ConnectedLdapPort = 0;
   }

   if (ConnectedLdapHost[0])
   {
      /* already connected to the required server */
      if (DebugWatch)
         WatchThis (__LINE__, "persistent %s:%d",
                    ConnectedLdapHost, ConnectedLdapPort);
   }
   else
   {
      strzcpy (ConnectedLdapHost, LdapHost, sizeof(ConnectedLdapHost));
      ConnectedLdapPort = LdapPort;
      if (!ConnectedLdapPort)
         if (!LdapSSL)
            ConnectedLdapPort = LDAP_PORT;
         else
            ConnectedLdapPort = DEFAULT_SSL_PORT;

      if (DebugWatch)
         WatchThis (__LINE__, "ldap_init() %s:%d",
                    ConnectedLdapHost, ConnectedLdapPort);

      ld = ldap_init (ConnectedLdapHost, ConnectedLdapPort);
      if (ld == NULL) 
      {
         if (DebugWatch)
            WatchThis (__LINE__, "ERROR %d %%X%08.08X", errno, vaxc$errno); 
         ServerUnbind = TRUE;
         return (FALSE); 
      }

      ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &LdapProtocolVersion);

      if (StartTLS || LdapSSL)
      {
         /******************/
         /* secure sockets */
         /******************/

         ldap_set_option (ld, LDAP_OPT_TLS_VERSION,
                          StartTLS ? &StartTLS : &LdapSSL);

         if (CertKeyFileName[0])
         {
            ldap_set_option (ld, LDAP_OPT_TLS_CERT_FILE, CertKeyFileName);
            ldap_set_option (ld, LDAP_OPT_TLS_PKEY_FILE, CertKeyFileName);

            /* turn on SYSPRV to allow access to possibly protected files */
            status = sys$setprv (1, &SysPrvMask, 0, 0);
            if (DebugWatch)
               if (VMSnok(status) || status == SS$_NOTALLPRIV)
                  WatchThis (__LINE__, "sys$setprv() %%X%08.08X\n", status);
         }

         if (DebugWatch)
            WatchThis (__LINE__, "ldap_tls_start() %s:%d",
                       StartTLS ? "StartTLS" : "LDAPS",
                       StartTLS ? StartTLS : LdapSSL);

         rc = ldap_tls_start (ld, StartTLS ? 1 : 0);

         /* turn off SYSPRV */
         if (CertKeyFileName[0]) sys$setprv (0, &SysPrvMask, 0, 0);

         if (rc != LDAP_SUCCESS)
         {
            if (DebugWatch)
               WatchThis (__LINE__, "ERROR %d %d %%X%08.08X %s",
                          rc, errno, vaxc$errno, ldap_err2string(rc)); 
            ServerUnbind = TRUE;
            return (FALSE); 
         }
      }

      if (LdapSimpleBind)
      {
         /* authenticate with simple username (well, dn=) and password */
         if (DebugWatch)
            WatchThis (__LINE__, "ldap_simple_bind_s() |%s|%s|",
                       LdapSimpleBindDn ? LdapSimpleBindDn : "(NULL)",
                       LdapSimpleBindPasswd ? LdapSimpleBindPasswd : "(NULL)");

         rc = ldap_simple_bind_s (ld, LdapSimpleBindDn,
                                      LdapSimpleBindPasswd);
         if (rc != LDAP_SUCCESS)
         { 
            if (DebugWatch)
               WatchThis (__LINE__, "ERROR %d %d %%X%08.08X %s",
                          rc, errno, vaxc$errno, ldap_err2string(rc)); 
            ServerUnbind = TRUE;
            return (FALSE); 
         }
      } 
   }

   /******************/
   /* make the query */
   /******************/

   if (DebugWatch)
      WatchThis (__LINE__, "ldap_search_s() |%s|%s|", LdapBase, UserFilter);

   rc = ldap_search_s (ld,
                       LdapBase, 
                       LDAP_SCOPE_SUBTREE,
                       UserFilter,
                       NULL, 0,
                       &resptr);

   if (rc != LDAP_SUCCESS)
   { 
      if (DebugWatch)
         WatchThis (__LINE__, "ERROR %d %d %%X%08.08X %s",
                    rc, errno, vaxc$errno, ldap_err2string(rc)); 
      ServerUnbind = TRUE;
      return (FALSE); 
   } 

   /*******************/
   /* process results */
   /*******************/

   PasswordValue[0] = UserDisplayValue[0] = UserNameValue[0] = '\0';

   EntryCount = 0;
   for (entptr = ldap_first_entry (ld, resptr);
        entptr != NULL; 
        entptr = ldap_next_entry (ld, entptr))
   { 
      EntryCount++;
      dnptr = ldap_get_dn (ld, entptr); 
      if (DoDump) fprintf (stdout, "\n%03.03d |%s|\n", EntryCount, dnptr); 
      ldap_memfree (dnptr); 

      for (attptr = ldap_first_attribute (ld, entptr, &berptr);
           attptr != NULL; 
           attptr = ldap_next_attribute (ld, entptr, berptr))
      { 
         if (DoDump) fprintf (stdout, "    |->|%s|\n", attptr); 

         AttValues = ldap_get_values (ld, entptr, attptr); 
         for (idx = 0; AttValues[idx] != NULL; idx++)
         {
            if (!strcmp (PasswordAttribute, attptr))
            {
               strzcpy (PasswordValue, AttValues[idx], sizeof(PasswordValue));
               if (DebugWatch)
                  WatchThis (__LINE__, "MATCH |%s|%s|",
                             PasswordAttribute, PasswordValue);
            }
            else
            if (!strcmp (UserDisplayAttribute, attptr))
            {
               strzcpy (UserDisplayValue, AttValues[idx],
                        sizeof(UserDisplayValue));
               if (DebugWatch)
                  WatchThis (__LINE__, "MATCH |%s|%s|",
                             UserDisplayAttribute, UserDisplayValue);
            }
            else
            if (!strcmp (UserNameAttribute, attptr))
            {
               strzcpy (UserNameValue, AttValues[idx], sizeof(UserNameValue));
               if (DebugWatch)
                  WatchThis (__LINE__, "MATCH |%s|%s|",
                             UserNameAttribute, UserNameValue);
            }

            if (DoDump)
            {
               if (!idx)
                  fprintf (stdout, "    |  |->|%s|\n", AttValues[idx]); 
               else
                  fprintf (stdout, "    |     |%s|\n", AttValues[idx]); 
            }
         }
         ldap_value_free (AttValues); 
         ldap_memfree (attptr); 
      } 

      if (berptr != NULL) ber_free (berptr, 0); 
   }

   if (DoDump)
      fprintf (stdout, "%s%d entries\n\n", EntryCount ? "\n" : "", EntryCount);

   if (DebugWatch) WatchThis (__LINE__, "ENTRIES %d", EntryCount);

   /* free the search results (let's hope it doesn't leak too much!) */ 
   ldap_msgfree (resptr); 

   if (EntryCount == 1)
   {
      /*******************/
      /* found one entry */
      /*******************/

      if (!PasswordValue[0])
      {
         /***********************/
         /* password attribute? */
         /***********************/

         /* can't do much in the way of authentication without this! */
         if (DebugWatch) WatchThis (__LINE__, "PASSWORD? (/PATT=)");
         fprintf (stdout, "500 ambiguous LDAP result.\n");
      }
      else
      if (!UserNameValue[0] && !UserNameDefault[0])
      {
         /***********************/
         /* username attribute? */
         /***********************/

         /* no username ('uid') and no default to fall back on */
         if (DebugWatch) WatchThis (__LINE__, "USERNAME? (/UATT=)");
         fprintf (stdout, "500 ambiguous LDAP result.\n");
      }
      else
      {
         PasswordOK = FALSE;

         if (CliAuthPassword[0])
            PasswordPtr = CliAuthPassword;
         else
            PasswordPtr = CgiLibVarNull ("AUTH_PASSWORD");
         if (!PasswordPtr)
         {
            if (DebugWatch) WatchThis (__LINE__, "AUTH_PASSWORD?");
            return (FALSE); 
         }

         if (VerifyDigest)
         {
            /*****************************/
            /* verify password by digest */
            /*****************************/

#if USE_OPENSSL
            if (!memcmp (PasswordValue, "{SSHA}", 6))
            {
               if (DebugWatch) WatchThis (__LINE__, "SSHA %s", PasswordValue+6);
               PasswordOK = VerifySSHA (PasswordValue, PasswordPtr);
            }
            else
            if (!memcmp (PasswordValue, "{SHA}", 5))
            {
               if (DebugWatch) WatchThis (__LINE__, "SHA %s", PasswordValue+5);
               PasswordOK = VerifySHA (PasswordValue, PasswordPtr);
            }
            else
            if (!memcmp (PasswordValue, "{SMD5}", 6))
            {
               if (DebugWatch) WatchThis (__LINE__, "SMD5 %s", PasswordValue+6);
               PasswordOK = VerifySMD5 (PasswordValue, PasswordPtr);
            }
            else
            if (!memcmp (PasswordValue, "{MD5}", 5))
            {
               if (DebugWatch) WatchThis (__LINE__, "MD5 %s", PasswordValue+5);
               PasswordOK = VerifyMD5 (PasswordValue, PasswordPtr);
            }
            else
#endif /* USE_OPENSSL */

            /* dangling 'else' from the macro */
            if (PasswordValue[0] != '{')
            {
               /* plain-text!! */
               PasswordOK = (strcmp (PasswordValue, PasswordPtr) == 0);
            }
            else
               if (DebugWatch) WatchThis (__LINE__, "ENCODING unknown");
         }
         else
         if (VerifySYSUAF)
         {
            /********************************/
            /* verify password using SYSUAF */
            /********************************/

            PasswordOK = VerifyAgainstSYSUAF (UserNameValue, PasswordPtr);
         }
         else
         if (VerifyLocal)
         {
            /************************************/
            /* verify using local functionality */
            /************************************/

            PasswordOK = VerifyLocalFunction (UserNameValue, PasswordPtr);
         }
         else
         {
            /*****************************/
            /* must be verified somehow! */
            /*****************************/

            if (DebugWatch) WatchThis (__LINE__, "VERIFY?");
         }

         /************/
         /* verified */
         /************/

         if (PasswordOK)
         {
            if (!UserNameValue[0])
            {
               if (DebugWatch)
                  WatchThis (__LINE__, "DEFAULT %s", UserNameDefault);
               strzcpy (UserNameValue, UserNameDefault, sizeof(UserNameValue));
            }
            fprintf (stdout, "100 VMS-USER %s\n", UserNameValue);
            if (UserDisplayValue[0])
            {
               /* must be done following VMS-USER which also sets USER! */
               fflush (stdout);
               fprintf (stdout, "100 USER %s\n", UserDisplayValue);
            }
         }
         else
            fprintf (stdout, "401 authentication failure.\n");
      }
   }
   else
   if (EntryCount > 1)
   {
      /********************/
      /* ambiguous result */
      /********************/

      if (DebugWatch) WatchThis (__LINE__, "AMBIGUOUS");
      fprintf (stdout, "500 ambiguous LDAP result.\n");
   }
   else
   {
      /*************/
      /* not found */
      /*************/

      fprintf (stdout, "401 authentication failure.\n");
   }

   /* flush the callout record */
   fflush (stdout);

   return (TRUE);
}

/*****************************************************************************/
/*
Verify the supplied username/password from the SYSUAF.

Get the specified user's flags, authorized privileges, quadword password, hash
salt and encryption algorithm from SYSUAF.  If any of specified bits in the
flags are set (e.g. "disusered") then fail the password authentication.
Using the salt and encryption algorithm hash the supplied password and compare
it to the UAF hashed password.

Return TRUE if the password is validated and there are no account restrictions,
FALSE otherwise.
*/ 

BOOL VerifyAgainstSYSUAF
(
char *UserNamePtr,
char *PasswordPtr
)
{
   /* UAI flags that disallow SYSUAF authentication */
   unsigned long  DisallowVmsFlags = UAI$M_DISACNT |
                                     UAI$M_PWD_EXPIRED |
                                     UAI$M_PWD2_EXPIRED |
                                     UAI$M_CAPTIVE |
                                     UAI$M_RESTRICTED | 0;

   static unsigned long  Context = -1;

   static unsigned long  UaiFlags,
                         UaiUic;
   static unsigned long  UaiPriv [2],
                         HashedPwd [2],
                         UaiPwd [2];
   static unsigned short  UaiSalt;
   static unsigned char  UaiEncrypt;

   static char UserName [15+1],
               Password [39+1];
   static $DESCRIPTOR (UserNameDsc, UserName);
   static $DESCRIPTOR (PasswordDsc, Password);

   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } UaiItems [] = 
   {
      { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 },
      { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 },
      { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 },
      { sizeof(UaiPwd), UAI$_PWD, &UaiPwd, 0 },
      { sizeof(UaiEncrypt), UAI$_ENCRYPT, &UaiEncrypt, 0 },
      { sizeof(UaiSalt), UAI$_SALT, &UaiSalt, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   char  *cptr, *sptr, *zptr;

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

   if (Debug) fprintf (stdout, "VerifyAgainstSYSUAF()\n");

   /* to upper case! (just truncate if too long) */
   zptr = (sptr = UserName) + sizeof(UserName)-1;
   for (cptr = UserNamePtr; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   UserNameDsc.dsc$w_length = sptr - UserName;

   if (DebugWatch) WatchThis (__LINE__, "SYSUAF |%s|", UserName);

   /* turn on SYSPRV to allow access to SYSUAF records */
   status = sys$setprv (1, &SysPrvMask, 0, 0);
   if (DebugWatch)
      if (VMSnok(status) || status == SS$_NOTALLPRIV)
         WatchThis (__LINE__, "sys$setprv() %%X%08.08X\n", status);

   status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status);

   /* turn off SYSPRV */
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status)) 
   {
      if (DebugWatch) WatchThis (__LINE__, "sys$getuai() %%X%08.08X", status);
      return (FALSE);
   }

   /* automatically disallow if any of these flags are set! */
   if (UaiFlags & DisallowVmsFlags)
   {
      if (DebugWatch) WatchThis (__LINE__, "FAILED on SYSUAF flags");
      return (FALSE);
   }

   /* to upper case! (just truncate if too long) */
   zptr = (sptr = Password) + sizeof(Password)-1;
   for (cptr = PasswordPtr; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   PasswordDsc.dsc$w_length = sptr - Password;

   status = sys$hash_password (&PasswordDsc, UaiEncrypt,
                               UaiSalt, &UserNameDsc, &HashedPwd);
   if (Debug) fprintf (stdout, "sys$hash_password() %%X%08.08X\n", status);
   if (VMSnok (status))
   {                            
      if (DebugWatch)
         WatchThis (__LINE__, "sys$hash_password() %%X%08.08X", status);
      return (FALSE);
   }

   if (HashedPwd[0] != UaiPwd[0] || HashedPwd[1] != UaiPwd[1])
   {
      if (DebugWatch) WatchThis (__LINE__, "FAILED password match");
      return (FALSE);
   }

   if (DebugWatch) WatchThis (__LINE__, "VERIFIED");

   return (TRUE);
}

/*****************************************************************************/
/*
Roll-you-own here!
*/

BOOL VerifyLocalFunction
(
char *StringPtr,
char *PasswordPtr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "VerifyLocalFunction() |%s|\n", StringPtr);

   return (FALSE);
}  

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

#if USE_OPENSSL

/*
Compare the supplied "{SHA}LcRNUdyib7gGaI8qq4wGNen46MoqL" SHA1 hash string to
the supplied plain-text password.
*/

BOOL VerifySHA
(
char *StringPtr,
char *PasswordPtr
)
{
   int  len;
   char  DecodeBuffer [20],
         PasswordHash [20];

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

   if (Debug) fprintf (stdout, "VerifySHA() |%s|\n", StringPtr);

   if (!memcmp (StringPtr, "{SHA}", 5)) StringPtr += 5;

   len = DecodeBase64 (StringPtr, strlen(StringPtr),
                       DecodeBuffer, sizeof(DecodeBuffer));
   if (len != 20) return (FALSE);

   HashSHA1 (PasswordPtr, strlen(PasswordPtr), NULL, 0, PasswordHash);
   if (!memcmp (DecodeBuffer, PasswordHash, 20)) return (TRUE);
   return (FALSE);
}  

/*****************************************************************************/
/*
Compare the supplied "{SSHA}LcRNUdyib7gGaI8qq4wGNen46MoqLrPH" Salted SHA1 hash
string to the supplied plain-text password.
*/

BOOL VerifySSHA
(
char *StringPtr,
char *PasswordPtr
)
{
   int  len;
   char  DecodeBuffer [48],
         PasswordHash [20];

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

   if (Debug) fprintf (stdout, "VerifySSHA() |%s|\n", StringPtr);

   if (!memcmp (StringPtr, "{SSHA}", 6)) StringPtr += 6;

   len = DecodeBase64 (StringPtr, strlen(StringPtr),
                       DecodeBuffer, sizeof(DecodeBuffer));
   if (len < 20 || len > 32) return (FALSE);

   HashSHA1 (PasswordPtr, strlen(PasswordPtr),
             DecodeBuffer+20, len-20, PasswordHash);

   if (!memcmp (DecodeBuffer, PasswordHash, 20)) return (TRUE);
   return (FALSE);
}  

/*****************************************************************************/
/*
Compare the supplied "{MD5}LcRNUdyib7gGaI8qq4wGNen46MoqL" MD5 hash string to
the supplied plain-text password.
*/

BOOL VerifyMD5
(
char *StringPtr,
char *PasswordPtr
)
{
   int  len;
   char  DecodeBuffer [16],
         PasswordHash [16];

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

   if (Debug) fprintf (stdout, "VerifyMD5() |%s|\n", StringPtr);

   if (!memcmp (StringPtr, "{MD5}", 5)) StringPtr += 5;

   len = DecodeBase64 (StringPtr, strlen(StringPtr),
                       DecodeBuffer, sizeof(DecodeBuffer));
   if (len != 16) return (FALSE);

   HashMD5 (PasswordPtr, strlen(PasswordPtr), NULL, 0, PasswordHash);
   if (!memcmp (DecodeBuffer, PasswordHash, 16)) return (TRUE);
   return (FALSE);
}  

/*****************************************************************************/
/*
Compare the supplied "{SMD5}LcRNUdy7gGaI8qq4wGNen46oqLrPm" Salted MD5 hash
string to the supplied plain-text password.
*/

BOOL VerifySMD5
(
char *StringPtr,
char *PasswordPtr
)
{
   int  len;
   char  DecodeBuffer [48],
         PasswordHash [20];

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

   if (Debug) fprintf (stdout, "VerifySMD5() |%s|\n", StringPtr);

   if (!memcmp (StringPtr, "{SMD5}", 6)) StringPtr += 6;

   len = DecodeBase64 (StringPtr, strlen(StringPtr),
                       DecodeBuffer, sizeof(DecodeBuffer));
   if (len < 16 || len > 32) return (FALSE);

   HashMD5 (PasswordPtr, strlen(PasswordPtr),
            DecodeBuffer+16, len-16, PasswordHash);

   if (!memcmp (DecodeBuffer, PasswordHash, 16)) return (TRUE);
   return (FALSE);
}  

/*****************************************************************************/
/*
Applies a (optionally Salted) SHA1 hash (RFC3112) to the supplied data.
*/

void HashSHA1
(
char *DataPtr,
int DataLength,
char *SaltPtr,
int SaltLength,
char *HashBuffer
)
{
   SHA_CTX  ctx;

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

   if (Debug) fprintf (stdout, "HashSHA1()\n");

   SHA1_Init (&ctx);
   SHA1_Update (&ctx, DataPtr, DataLength);
   if (SaltPtr) SHA1_Update (&ctx, SaltPtr, SaltLength);
   SHA1_Final ((unsigned char*)HashBuffer, &ctx);
}  

/*****************************************************************************/
/*
Applies a (optionally Salted) MD5 hash (RFC1321) to the supplied data.
*/

void HashMD5
(
char *DataPtr,
int DataLength,
char *SaltPtr,
int SaltLength,
char *HashBuffer
)
{
   MD5_CTX  ctx;

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

   if (Debug) fprintf (stdout, "HashMD5()\n");

   MD5_Init (&ctx);
   MD5_Update (&ctx, DataPtr, DataLength);
   if (SaltPtr) MD5_Update (&ctx, SaltPtr, SaltLength);
   MD5_Final ((unsigned char*)HashBuffer, &ctx);
}  

/*****************************************************************************/
/*
Using OpenSSL BIO streams decode base-64 into a plain-string.  And yes, it
does seem a bit of an overkill but as we're using OpenSSL for the SHA1 hashing
why not just use as much more as we need!
*/

int DecodeBase64
(
char *Base64Ptr,
int Base64Length,
char *BufferPtr,
int SizeOfBuffer
)
{
   static BIO  *bio,
               *BioB64Ptr,
               *BioMemPtr;
   int  rc;

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

   if (Debug) fprintf (stdout, "DecodeBase64()\n");

   if (!BioMemPtr)
   {
      BioMemPtr = BIO_new (BIO_s_mem());
      if (!BioMemPtr) exit (SS$_BUGCHECK);
      BioB64Ptr = BIO_new (BIO_f_base64());
      if (!BioB64Ptr) exit (SS$_BUGCHECK);
      BIO_set_flags (BioB64Ptr, BIO_FLAGS_BASE64_NO_NL);
      bio = BIO_push (BioB64Ptr, BioMemPtr);
      if (!bio) exit (SS$_BUGCHECK);
   }

   if (Base64Length == -1) Base64Length = strlen(Base64Ptr);
   BIO_reset (bio);
   rc = BIO_write (BioMemPtr, Base64Ptr, Base64Length);
   rc = BIO_flush (bio);
   rc = BIO_pending (bio);
   if (rc > SizeOfBuffer) rc = SizeOfBuffer;
   rc = BIO_read (bio, BufferPtr, rc);
   if (Debug) fprintf (stdout, "|%s|\n", BufferPtr);

   return (rc);
}  

#endif /* USE_OPENSSL */

/*****************************************************************************/
/*
Prevent the great unwashed from pillaging the treasures within!
*/ 

void NeedsPrivilegedAccount ()

{
   static unsigned long  PrivAcctMask [2] = { PRV$M_SETPRV | PRV$M_SYSPRV, 0 };

   static long  Pid = -1;
   static unsigned long  JpiAuthPriv [2];

   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      void  *buf_addr;
      void  *ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiAuthPriv), JPI$_AUTHPRIV, &JpiAuthPriv, 0 },
      {0,0,0,0}
   };

   int  status;

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

   if (Debug) fprintf (stdout, "NeedsPrivilegedAccount()\n");

   status = sys$getjpiw (0, &Pid, 0, &JpiItems, 0, 0, 0);
   if (VMSnok (status)) exit (status);

   if (!(JpiAuthPriv[0] & PrivAcctMask[0])) exit (SS$_NOSYSPRV);
}

/*****************************************************************************/
/*
Output and flush a record suitable for WASD to WATCH in callout mode.
*/ 

void WatchThis
(
int SourceCodeLine,
char *FormatString,
...
)
{
   static unsigned long  PrevBinTime [2];
   static char  TimeString [12];
   static $DESCRIPTOR (TimeFaoDsc, "!2ZL:!2ZL:!2ZL.!2ZL\0");
   static $DESCRIPTOR (TimeStringDsc, TimeString);

   int  argcnt;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];
   va_list  argptr;

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

   if (Debug) fprintf (stdout, "WatchThis()\n");

   sys$gettim (&BinTime);
   if (BinTime[0] != PrevBinTime[0] ||
       BinTime[1] != PrevBinTime[1])
   {
      PrevBinTime[0] == BinTime[0];
      PrevBinTime[1] == BinTime[1];
      sys$numtim (&NumTime, &BinTime);
      sys$fao (&TimeFaoDsc, NULL, &TimeStringDsc,
               NumTime[3], NumTime[4], NumTime[5], NumTime[6]);
   }

   fprintf (stdout, "000 [%d] %s ", SourceCodeLine, TimeString);
   va_count (argcnt);
   va_start (argptr, FormatString);
   vprintf (FormatString, argptr);
   fputs ("\n", stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Return a pointer to a string showing the elapsed time (for WATCHing).
*/ 

char* WatchElapsed ()

{
   static unsigned long  StatTimerContext;
   static unsigned long  StatTimerElapsedTime = 1;
   static char  ElapsedTime [24];
   static $DESCRIPTOR (ElapsedFaoDsc, "!%D\0");
   static $DESCRIPTOR (ElapsedTimeDsc, ElapsedTime);

   int  status;
   unsigned long  ElapsedBinTime [2];

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

   if (Debug) fprintf (stdout, "WatchElapsed()\n");

   if (StatTimerContext)
   {
      status = lib$stat_timer (&StatTimerElapsedTime,
                               &ElapsedBinTime,
                               &StatTimerContext);
      if (VMSnok (status)) exit (status);

      sys$fao (&ElapsedFaoDsc, NULL, &ElapsedTimeDsc, &ElapsedBinTime);
      memmove (ElapsedTime, ElapsedTime+5, 12);

      status = lib$free_timer (&StatTimerContext);
      if (VMSnok (status)) exit (status);
      StatTimerContext = 0;

      return (ElapsedTime);
   }

   /* initialize statistics timer */
   status = lib$init_timer (&StatTimerContext);
   if (VMSnok (status)) exit (status);

   return (NULL);
}

/*****************************************************************************/
/*
Translate a logical name using LNM$FILE_DEV. Return a pointer to the value
string, or NULL if the name does not exist.  If 'LogValue' is supplied the
logical name is translated into that (assumed to be large enough), otherwise
it's translated into an internal static buffer.
*/

char* SysTrnLnm
(
char *LogName,
char *LogValue
)
{
   static unsigned short  ShortLength;
   static char  StaticLogValue [256];
   static $DESCRIPTOR (LogNameDsc, "");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { 255, LNM$_STRING, 0, &ShortLength },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;

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

   if (Debug) fprintf (stdout, "SysTrnLnm() |%s|\n", LogName);

   LogNameDsc.dsc$a_pointer = LogName;
   LogNameDsc.dsc$w_length = strlen(LogName);
   if (LogValue)
      cptr = LnmItems[0].buf_addr = LogValue;
   else
      cptr = LnmItems[0].buf_addr = StaticLogValue;

   status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (!(status & 1))
   {
      if (Debug) fprintf (stdout, "|(null)|\n");
      return (NULL);
   }

   cptr[ShortLength] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", cptr);
   return (cptr);
}

/*****************************************************************************/
/*
Parses the string passed to it as a 'command-line' with parameters and
qualifiers.  Makes the real command-line and the authentication configuration
parameters look and behave in the same fashion.
*/

void GetRunTimeParameters
(
char *clptr,
BOOL FromCli
)
{
   char  ch;
   char  *aptr, *cptr, *sptr, *zptr;

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

   if (Debug) fprintf (stdout, "RunTimeParameters() |%s|\n", clptr);

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr && *aptr == '/') *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (FromCli && strsame (aptr, "/AUTH_PASSWORD=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (CliAuthPassword, cptr, sizeof(CliAuthPassword));
         continue;
      }

      if (strsame (aptr, "/BASE=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (LdapBase, cptr, sizeof(LdapBase));
         continue;
      }

      if (strsame (aptr, "/CERT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (CertKeyFileName, cptr, sizeof(CertKeyFileName));
         continue;
      }

      if (strsame (aptr, "/DATT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (UserDisplayAttribute, cptr, sizeof(UserDisplayAttribute));
         continue;
      }

      if (strsame (aptr, "/DIGEST", 4))
      {
         VerifyDigest = TRUE;
         continue;
      }

      if (FromCli && strsame (aptr, "/DUMP", 4))
      {
         DoDump = TRUE;
         continue;
      }

      if (FromCli && strsame (aptr, "/DBUG", -1))
      {
         Debug = TRUE;
         continue;
      }

      if (strsame (aptr, "/FILTER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (LdapFilter, cptr, sizeof(LdapFilter));
         continue;
      }

      if (strsame (aptr, "/HOST=", 4) ||
          strsame (aptr, "/SERVER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (LdapHost, cptr, sizeof(LdapHost));
         for (cptr = LdapHost; *cptr && *cptr != ':'; cptr++);
         if (*cptr)
         {
            *cptr++ = '\0';
            if (isdigit(*cptr)) LdapPort = (short)atoi(cptr);
         }
         continue;
      }

      if (strsame (aptr, "/LOCAL", 4))
      {
         VerifyLocal = TRUE;
         continue;
      }

      if (strsame (aptr, "/PATT=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (PasswordAttribute, cptr, sizeof(PasswordAttribute));
         continue;
      }

      if (strsame (aptr, "/PORT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         LdapPort = (short)atoi(cptr);
         continue;
      }

      if (FromCli && strsame (aptr, "/REMOTE_USER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (CliRemoteUser, cptr, sizeof(CliRemoteUser));
         continue;
      }

      if (strsame (aptr, "/RESET", 4))
      {
         DoDump =
            VerifySYSUAF =
            VerifyDigest = FALSE;

         LdapPort = LdapSSL = StartTLS = 0;
         LdapProtocolVersion = LDAP_VERSION3;

         CertKeyFileName[0] =
            CliAuthPassword[0] =
            CliRemoteUser[0] =
            LdapBase[0] =
            LdapFilter[0] =
            LdapHost[0] =
            LdapSimpleBindDn[0] =
            LdapSimpleBindPasswd[0] =
            PasswordAttribute[0] =
            UserDisplayAttribute[0] =
            UserNameDefault[0] =
            UserNameAttribute[0] = '\0';

         strcpy (PasswordAttribute, DEFAULT_ATTRIBUTE_PASSWORD);
         strcpy (UserDisplayAttribute, DEFAULT_ATTRIBUTE_DISPLAY);
         strcpy (UserNameAttribute, DEFAULT_ATTRIBUTE_USERNAME);

         continue;
      }

      if (strsame (aptr, "/SIMPLE=", 4))
      {
         LdapSimpleBind = TRUE;
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (LdapSimpleBindDn, cptr, sizeof(LdapSimpleBindDn));
         for (cptr = LdapSimpleBindDn; *cptr && *cptr != ':'; cptr++)
            if (*cptr == '\\') RemoveSlash (cptr);
         if (*cptr)
         {
            *cptr++ = '\0';
            strzcpy (LdapSimpleBindPasswd, cptr, sizeof(LdapSimpleBindPasswd));
            for (cptr = LdapSimpleBindPasswd; *cptr && *cptr != ':'; cptr++)
               if (*cptr == '\\') RemoveSlash (cptr);
         }
         continue;
      }

      if (strsame (aptr, "/SSL=", 4))
      {
         LdapSSL = 1;  /* the default; TLSv1 */
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         LdapSSL = atoi(cptr);
         continue;
      }

      if (strsame (aptr, "/TLS=", 4))
      {
         StartTLS = 1;  /* the default; TLSv1 */
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         StartTLS = atoi(cptr);
         continue;
      }

      if (strsame (aptr, "/SYSUAF", 4))
      {
         VerifySYSUAF = TRUE;
         continue;
      }

      if (strsame (aptr, "/UDEF=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (UserNameDefault, cptr, sizeof(UserNameDefault));
         continue;
      }

      if (strsame (aptr, "/UATT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         strzcpy (UserNameAttribute, cptr, sizeof(UserNameAttribute));
         continue;
      }

      if (strsame (aptr, "/VERSION=", 4))
      {
         LdapProtocolVersion = LDAP_VERSION3;
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (!*cptr) continue;
         cptr++;
         LdapProtocolVersion = atoi(cptr);
         continue;
      }

      if (FromCli && strsame (aptr, "/WATCH", -1))
      {
         DebugWatch = TRUE;
         continue;
      }

      if (FromCli)
      {
         if (*aptr == '/')
         {
            fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                     Utility, aptr+1);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         else
         {
            fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                     Utility, aptr);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
      }
   }
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line.
*/

void GetParameters ()

{
   static char  CommandLine [512];
   static unsigned long  Flags = 0;

   int  status;
   unsigned short  Length;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if (Debug) fprintf (stdout, "GetParameters()\n");

   /* get the entire command line following the verb */
   status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags);
   if (VMSnok (status)) exit (status);

   CommandLine[Length] = '\0';
   GetRunTimeParameters (CommandLine, TRUE);
}

/****************************************************************************/
/*
Remove the '\' for slash-escaped characters by shuffling all the remaining
characters left.  Elementary but enough for this.
*/ 

void RemoveSlash (char *cptr)

{
   char  *sptr;

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

   if (!*(sptr = cptr+1)) return;
   while (*sptr) *cptr++ = *sptr++;
   *cptr = '\0';
}

/****************************************************************************/
/*
Copy a string without overflow (or indication of it :-).
*/ 

int strzcpy
(
char *String,
char *cptr,
int SizeOfString
)
{
   char  *sptr, *zptr;

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

   if (SizeOfString) SizeOfString--;
   zptr = (sptr = String) + SizeOfString;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   return (sptr - String);
}

/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
TRUE if two strings are the same, or FALSE if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

BOOL strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (FALSE);
      if (count)
         if (!--count) return (TRUE);
   }
   if (*sptr1 || *sptr2)
      return (FALSE);
   else
      return (TRUE);
}

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

