          <<< NOTED::DISK$NOTES7:[NOTES$LIBRARY_7OF4]HACKERS.NOTE;1 >>>
                               -< ** Hackers ** >-
================================================================================
Note 1541.3            sys$check_access and non-priv users                3 of 8
UNIFIX::HARRIS "Juggling has its ups and downs"     895 lines  21-JUL-1995 08:45
            -< $CHKPRO() system service example w/useful comments >-
--------------------------------------------------------------------------------
 /****************************************************************************
 *                                                                           *
 *  Copyright (c) 1990, 1995                                                 *
 *  by DIGITAL Equipment Corporation, Maynard, Mass.                         *
 *                                                                           *
 *  This software is furnished under a license and may be used and  copied   *
 *  only  in  accordance  with  the  terms  of  such  license and with the   *
 *  inclusion of the above copyright notice.  This software or  any  other   *
 *  copies  thereof may not be provided or otherwise made available to any   *
 *  other person.  No title to and ownership of  the  software  is  hereby   *
 *  transferred.                                                             *
 *                                                                           *
 *  The information in this software is subject to change  without  notice   *
 *  and  should  not  be  construed  as  a commitment by DIGITAL Equipment   *
 *  Corporation.                                                             *
 *                                                                           *
 *  DIGITAL assumes no responsibility for the use or  reliability  of  its   *
 *  software on equipment which is not supplied by DIGITAL.                  *
 *                                                                           *
 ****************************************************************************/
/*---------------------------------------------------------------------------
    CHKPRO.C - This module contains example code on using the SYS$CHKPRO
        system service.  There are lots of valuable comments throughout the
        code which give suggestions and guidelines for how the code should and 
        should not be used.  In addition, I would strongly suggest that you
        study the code so you will be able to debug your application when your 
        implementation of security breaks (and it will :-)  Or even worse, the 
        rules of VMS security changes and you need to adapt with it.
-----------------------------------------------------------------------------
  29-Jun-1990   R. A. Harris    o  Initial coding.
   7-Jun-1991   R. A. Harris    o  Add more comments and documentation on how 
                                   different parts of the program (or
                                   SYS$CHKPRO) works.  It also contains
                                   valuable suggestions on how the routines
                                   should and should not be used.  There are
                                   also suggestions on extendions that may be
                                   added to make the code more useful.
  19-Jul-1995   K. G. Blaylock  o  Add in support for reading larger ACLs
-----------------------------------------------------------------------------*/

#include <stdio.h>
#include <descrip.h>
#include <ssdef.h>
#include <uaidef.h>
#include <chpdef.h>
#include <armdef.h>
#include <rmsdef.h>
#include <fab.h>
#include <xabprodef.h>

#ifdef DEBUG
    /*
      Setting the sizes to smaller values allows some of the overflow code
      paths to be tested without having to add large numbers of rights
      identifiers to an accunt and without having to add large numbers of ACEs 
      to files.
    */
#   define INITIAL_ID_SIZE 1
#   define ID_SIZE_INCREMENT 1
#   define ACL_STACK_ALLOCATION 8
#else
#   define INITIAL_ID_SIZE 128
#   define ID_SIZE_INCREMENT 128
#   define ACL_STACK_ALLOCATION 128
#endif /* DEBUG */

#define ERROR -1

struct itmlst {
    short buflen;
    short itmcod;
    void *bufadr;
    void *retlen;
};



unsigned long *get_users_uaf_rights( unsigned char *username, 
                                     unsigned short *number_rights,
                                     unsigned long *user_privs )
/*---------------------------------------------------------------------------
   get_users_uaf_rights - will retrive all the rights of the user specified,
        from the UAF and Rights database.
        
        This routine is the most expensive of all the routines in this module.
        This is because it has to perform I/O to the SYSUAF.DAT file for the
        user, and one I/O to the RIGHTSLIST.DAT file for each Right Identifier 
        attached to the user account.
        
        Therefore, you only want to call this routine once when a user makes a 
        connection to your server and then store the returned information in a 
        data structure which you can later use with the 'verify_access'
        routine each time you need to check if that user has access to a file.
        
        If you are only going to perform 1 access check for a user during the
        life of your program, then maybe you just want to use SYS$CHECK_ACCESS,
        since it is less trouble, and will internally do all the stuff we are
        doing in this module.
  
   FORMAL PARAMETERS:
  
        username        - VMS username. - INPUT
        number_rights   - return number of user rights found.   - INPUT pointer
        user_privs      - return the user's default privileges  - INPUT pointer
  
   IMPLICIT INPUTS:
  
        None
  
   IMPLICIT OUTPUTS:
  
        None
  
   ROUTINE VALUE and COMPLETION CODES:
  
        Returns address of rightslist suitable for use by SYS$CHKPRO() 
                        if operation succeed.
                
        Return  ERROR (-1) if operation failed.
  
   SIDE EFFECTS:
  
        none
  
-----------------------------------------------------------------------------*/
{
    unsigned long status;
    unsigned long uic[2];
    unsigned long context;
    unsigned long num_id;
    unsigned long *id_list;
    
    int used_malloc = 0;
    int storage_size = INITIAL_ID_SIZE;
    unsigned long stack_id_storage[INITIAL_ID_SIZE*2];
    unsigned long *id_ptr = stack_id_storage;
    unsigned long *current_ptr = stack_id_storage;
    unsigned long *old_ptr = NULL;
    
    struct {
        short buflen;
        short item_code;
        void *buffer_address;
        void *return_length_address;
    } itmlst[3];
    
    struct dsc$descriptor_s username_desc;

/*---------------------------------------------------------------------------*/
/*
  Fetch the user's UIC and default privileges.
*/
    itmlst[0].buflen = sizeof(long);
    itmlst[0].item_code = UAI$_UIC;
    itmlst[0].buffer_address = uic;
    itmlst[0].return_length_address = NULL;
    
    itmlst[1].buflen = sizeof(long)*2;
    itmlst[1].item_code = UAI$_DEF_PRIV;
    itmlst[1].buffer_address = user_privs;
    itmlst[1].return_length_address = NULL;
    
    itmlst[2].buflen = 0;
    itmlst[2].item_code = 0;
    
    username_desc.dsc$w_length = strlen(username);
    username_desc.dsc$b_class = DSC$K_CLASS_S;
    username_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    username_desc.dsc$a_pointer = username;
    
    status = sys$getuai( 0, 0, &username_desc, itmlst, 0, 0, 0 );
    if ( (status & 1) != 1 )
    {
        fprintf( stderr, 
            "get_users_uaf_rights: sys$getuai error\n" );
        lib$signal( status );
        return( ERROR );
    }
    uic[1] = 0;
/*
  If you do a SHOW PROCESS/PRIVILEGES from an interactive, batch, or network
  job, and you will see some additional Rights Identifiers which are added by
  LOGINOUT.  Rights identifiers such as "INTERACTIVE", or "BATCH", or
  "NETWORK".  For Interactive users LOGINOUT attaches "LOCAL", "REMOTE", or
  "DIALIN" depending on how the terminal session is attached.  There is also a 
  Rights Identifier for the node of the system the user is logged into
  ("SYS$NODE_nodename" where 'nodename' is the DECnet node name).
  
  You may wish to use the SYS$ASCTOID system service to fetch the value for
  one of these identifiers and attach them to the beginning of the user's
  rights list before starting the next section.
  
  Another idea is that maybe you want to attach a Rights Identifier of your
  own which is associated with your application.  For example, lets say your
  application is called "FREDS_CRUNCHER.EXE".  Maybe you would like to attach
  your own Rights Identifer "FREDS_CRUNCHER".  Then you can allow your program 
  to access certant files regardless of who the user is, or deny your program
  access regardless of who your user is.
  
  You would have to make sure that the rights identifier existed in the Rights 
  Database before you could use SYS$ASCTOID on it.  One way is to use the
  AUTHORIZE routine, the other is to have your program use the SYS$ADD_IDENT
  system service when your program starts-up.
  
  NOTE:  if you decide to add such identifiers, you may want to modify the
         DEBUG constants at the beginning of this module to account for the
         extra identifiers added before testing to see if the allocated memory 
         needs to be expanded.  Just thought you may want to know :-)
*/

/*
  Fetch the rights identifiers for this user.
*/
    num_id = 0;
    context = 0;
    for( ;; )
    {
    /*
      Fetch the next rights identifier.
    */
        status = sys$find_held(uic, id_ptr, id_ptr+1, &context);
        if (! (status & 1)) 
            break;
        id_ptr += 2;            /* point to next storage location */
        num_id++;               /* increment actual number of id count */
    /*
      See if there is any room left in the current storage location.
    */
        if (num_id >= storage_size) 
        {
        /*
          All out of space.  Calculate a new size for the storage location
          and then allocate some new memory for the storage.
        */
            old_ptr = current_ptr;
            storage_size += ID_SIZE_INCREMENT;  
            if ((current_ptr = malloc(storage_size*2*sizeof(long))) == NULL) 
            {
            /*
              Ran out of memory.  Report an error and free up any memory we
              are currently holding.  Then return failure.
            */
                if ( used_malloc != 0 ) 
                    free( old_ptr );
                fprintf( stderr, 
                    "get_users_uaf_rights: Memory allocation error\n" );
                return( ERROR );
            }
        /*
          Copy the accumulated rights indentifiers into the new memory.
        */
            memcpy( current_ptr, old_ptr, 
                2*sizeof(long)*(storage_size - ID_SIZE_INCREMENT) );
        /*
          Free up the old memory if it was allocated memory.
        */
            if ( used_malloc != 0 ) 
                free( old_ptr );
            used_malloc++;
        /*
          Calculate the new position for storing the next rights identifier
          in the new storage.
        */
            id_ptr = current_ptr 
                   + (2 * (storage_size - ID_SIZE_INCREMENT));
        }
    }
/*
  Check for errors.
*/
    if (status != SS$_NOSUCHID) 
    {
    /*
      Things didn't go as well as expected.  Free up memory and signal an
      error.
    */
        if ( used_malloc != 0 )
            free( current_ptr );
                
        sys$finish_rdb(&context);
        
        fprintf( stderr, "get_users_uaf_rights: SYS$FIND_HELD failure\n" );
        lib$signal( status );
        return( ERROR );
    }
/*
  Allocate the exact size of memory needed to store all the identifiers 
*/
    ++num_id;           /* increase one for the user_id */
    if ((id_list = malloc(num_id*2*sizeof(long))) == NULL) 
    {
    /*
      Ran out of memory.  Lets tell the world.
    */
        if ( used_malloc != 0 )
            free( current_ptr );
        fprintf( stderr, "get_users_uaf_rights: Memory allocation error\n" );
        return( ERROR );
    }
/*
  Copy the found identifiers into the allocated memory along with the UIC of
  the user at the very beginning of the list.
*/
    id_ptr = id_list;
    *id_ptr++ = uic[0];
    *id_ptr++ = 0;
    memcpy( id_ptr, current_ptr, (num_id - 1)*2*sizeof(long) );
/*
  Free up any scratch memory we may have allocated.
*/
    if ( used_malloc != 0 )
        free( current_ptr );
/*
  Return the address of the allocated memory containing the user's rights
  identifiers.
*/
    *number_rights = num_id;
    
    return(id_list);
}




unsigned long *get_file_access_rules(   char *file, 
                                        unsigned short *acllen,
                                        unsigned long *uic,
                                        unsigned short *protection )
/*---------------------------------------------------------------------------
    get_file_access_rules - routine to fetch the files owner UIC, the files
        SOGW protection mask, and any ACLs attached to the file.
        
        This routine only returns the information for the target file.  Hence
        this information can only be used by 'verify_access' to check the
        user's access rights to the specified file.  If you want to verify
        that the user is also allowed to access the directories between
        [000000] and the targe file, then you will need to check each
        directory individually.  You will also need to do the parsing
        necessary to walk the directory path.
        
        If you are really interested in high performance, then scrap the RMS
        calls and substitute direct calls to the XQP via the QIO interface.
        This is is not for the faint of heart, so you should be sure switching 
        to direct XQP calls is really going to buy the performance
        improvements you think it is.  Because if you think the SYS$CHKPRO
        documentation in the V5 System Services manual is confusing, then your 
        just going to love the ACP QIO Interface documentation in the I/O
        user's guide :-)
   
   FORMAL PARAMETERS:
  
        file            IN -- pointer to a nul terminated C string containing
                              the file path specification
                              (disk:[dir.dir]file.typ)
        acllen          OUT - pointer to a short where the length of the
                              Access  Control List (ACL) which was attached to 
                              the file.
        uic             OUT - pointer to a long where the file's owner UIC will
                              will be returned.
        protection      OUT - pointer to a short where the files System,
                              Owner, Group, World (SOGW) protection mask will
                              be returned.  The mask actually contains 1 bits
                              to deny access, and zero bits to allow access.
                              
                              15                                     0
                              _________________________________________
                              | D E W R | D E W R | D E W R | D E W R |
                              |_________|_________|_________|_________|
                                 World     Group     Owner     System
  
  
   IMPLICIT INPUTS:
  
        None
  
   IMPLICIT OUTPUTS:
  
        None
  
   ROUTINE VALUE and COMPLETION CODES:
  
        Return 0 if operation failed.
        Return a pointer to the Access Control List (ACL) for the file.  It is 
               in a format suitable for passing directly to 'verify_access'.
  
   SIDE EFFECTS:
  
        none
  
-----------------------------------------------------------------------------*/
{
#define __MAX(a,b) ( (a) > (b) ? (a) : (b) )
#define __MIN(a,b) ( (a) < (b) ? (a) : (b) )

    unsigned long status;
    struct FAB fab;
    struct XABPRO xabpro;
    unsigned long  acl_stack_buffer[ACL_STACK_ALLOCATION];
    unsigned long *aclbuf = acl_stack_buffer;
    unsigned char *p;
    unsigned char *e;
    unsigned long aclsiz = sizeof(acl_stack_buffer);
    
/*---------------------------------------------------------------------------*/
#ifdef DEBUG
    memset( acl_stack_buffer, 255, sizeof(acl_stack_buffer) );
#endif /* DEBUG */
/*
  Initialize the RMS information
*/
    fab = cc$rms_fab;
    fab.fab$l_fna = file;
    fab.fab$b_fns = strlen(file);
    fab.fab$b_fac = FAB$M_GET;
    fab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRUPD | FAB$M_SHRPUT;
    fab.fab$l_xab = &xabpro;
    xabpro = cc$rms_xabpro;
    xabpro.xab$l_aclbuf = aclbuf;
    xabpro.xab$w_aclsiz = aclsiz;
/*
  Open the file and fetch the ACL and protection information.
*/
    status = sys$open( &fab, 0, 0 );
    if ( (status & 1) != 1 )
    {
        fprintf( stderr, "get_file_access_rules: sys$open error\n" );
        lib$signal( status, fab.fab$l_stv );
        return( ERROR );
    }
    else if ( (xabpro.xab$l_aclsts & 1) != 1 
         &&    xabpro.xab$l_aclsts != SS$_ACLEMPTY )
    {
        fprintf( stderr, "get_file_access_rules: sys$open error\n" );
        lib$signal( xabpro.xab$l_aclsts );
        return( ERROR );
    }
/*
  Allocate sufficient memory to hold all the ACEs in the ACL and copy what we
  have received so far into the memory.
*/
    if ( xabpro.xab$l_aclsts == SS$_ACLEMPTY )
    {
        aclbuf = NULL;
    }
    else
    {
        aclbuf = malloc( xabpro.xab$w_acllen );
        if ( aclbuf == NULL )
        {
            fprintf( stderr, 
                "get_file_access_rules: Memory allocation error\n" );
            return( ERROR );
        }
        memcpy( aclbuf, acl_stack_buffer, 
        __MIN(sizeof(acl_stack_buffer),xabpro.xab$w_acllen) );
    /*
      If the number of ACEs in the file exceeds the size of our buffer, then
      go get the rest of them.

        Loop through the ACL buffer with multiple calls to $DISPLAY. It
        will update the xabpro.xab$l_aclctx field to get the next set of
        aces.  Do this until the ACLSTS field is not successful
        (SS$_NOMOREACES) or we have run out of buffers.  Note that
        with the ACLSIZ field at zero, we do not update the context
        field and cannot 'end' the loop; the loop termination is
        the aclsiz field equaling zero.
    */
        if ( xabpro.xab$w_acllen > sizeof(acl_stack_buffer) )
        {
            p = aclbuf ;
            while ((xabpro.xab$l_aclsts&1) && (xabpro.xab$w_aclsiz > 0))
            {
                /*
                Find the end of the already transferred ACEs.
                */
                for( e=p+sizeof(acl_stack_buffer); 
                     *p != 0 && p < e; 
                     p = p + *p )
                    ;

                xabpro.xab$w_aclsiz = 
                    __MIN( xabpro.xab$w_acllen - (p - (char *)aclbuf), aclsiz) ;
                xabpro.xab$l_aclbuf = p;
                /*
                Fetch the next set of ACEs.
                */
                status = sys$display( &fab, 0, 0 );
                if ( (status & 1) != 1 )
                {
                    fprintf( stderr, 
                        "get_file_access_rules: sys$display error\n" );
                    lib$signal( status, fab.fab$l_stv );
                    return( ERROR );
                }
            }
        }
    }
/*
  Close the file.
*/
    status = sys$close( &fab, 0, 0 );
/*
  Return additional file access information.
*/
    *acllen = xabpro.xab$w_acllen;
    *uic = xabpro.xab$l_uic;
    *protection = xabpro.xab$w_pro;
    return( aclbuf );
}




unsigned long verify_access(    unsigned long file_uic, 
                                unsigned long access_mode, 
                                struct identifier_ace *acl, 
                                unsigned long acl_len, 
                                unsigned short prot_code, 
                                unsigned int *priv, 
                                unsigned long *rights_list,
                                unsigned short num_rights, 
                                unsigned int *addrights 
                           )
/*---------------------------------------------------------------------------
    Verify_access will verify if the accessor can access a file based
        on his privilege, etc... passed as parameters.
  
  
   FORMAL PARAMETERS:
  
        file_uic        - UIC code of the file. - INPUT Value
        access_mode     - Mode of access. (read, write, etc.) - INPUT value.
        acl             - access control list.  - INPUT structure pointer.
        acl_len         - length of acl.        - INPUT value.
        prot_code       - file protection code. - INPUT value.
        priv            - accessor's privilege. - INPUT value.
        rights_list     - accessor's right list.- INPUT pointer.
        num_rights      - accessor's right list length.  - INPUT value.
        add_rights      - additional rights for the accessor - INPUT pointer.
  
  
   IMPLICIT INPUTS:
  
        None
  
   IMPLICIT OUTPUTS:
  
        None
  
   ROUTINE VALUE and COMPLETION CODES:
  
        Returns 1 if operation succeed.
        Return  0       if operation failed.
  
   SIDE EFFECTS:
  
        none
  
-----------------------------------------------------------------------------

    The most difficult thing to understand about SYS$CHKPRO is the CHP$_PROT
    item list entry.  The CHP$_PROT argument works this way:
    
         |      |       |       |
         |      |       |       |
         v      v       v       v
       World  Group   Owner  System
    15                             0
    _________________________________
    |D E W R|D E W R|D E W R|D E W R|  00    Typically put file SOGW bits here
    |_______|_______|_______|_______|
    |      C|      C|      C|      C|  02    Set whether class has Control
    |_______|_______|_______|_______|          Typically S:c, O:c, G,W (0x1100)
    | future| future| future| future|  04      and remember bits are inverse.
    |_______|_______|_______|_______|        The rest of the bits are for
    | future| future| future| future|  06      future use and are undefined
    |_______|_______|_______|_______|          today.
    | future| future| future| future|  08
    |_______|_______|_______|_______|
    | future| future| future| future|  10
    |_______|_______|_______|_______|
    | future| future| future| future|  12
    |_______|_______|_______|_______|
    | future| future| future| future|  14
    |_______|_______|_______|_______|
    
    
    Think of the above 8 words as above as creating 4 columns.  Each column 
    represents 1 longword for System, Owner, Group, and World classes of
    users.  What SYS$CHKPRO does internally, is to convert each column into 
    its own longword.  There is one long word internally for System, one
    for Owner, one for Group, and one for World.  Internally it looks like
    the following:
  
  31                                                      0
  __________________________________________________________
  |future|future|future|future|future|future|     C|D E W R| 00  <-- System
  |______|______|______|______|______|______|______|_______|
  |future|future|future|future|future|future|     C|D E W R| 04  <-- Owner
  |______|______|______|______|______|______|______|_______|
  |future|future|future|future|future|future|     C|D E W R| 08  <-- Group
  |______|______|______|______|______|______|______|_______|
  |future|future|future|future|future|future|     C|D E W R| 12  <-- World
  |______|______|______|______|______|______|______|_______|
  
    You will notice that this looks exactly like the Access protection mask 
    for a ACL Identifier ACE.  This is intentional, so that checking file
    SOGW access checks and ACL access checks can be performed by the same
    logic.  The format of CHP$_PROT also allows the existing 2 byte SOGW
    file protection bit mask to be passed into SYS$CHKPRO without the
    application program performing the above conversion and it allows for
    the future expansion of protection bits to allow for up to 32 bits of
    unique protection types for each class of SOGW.  That expansion has
    already started if you consider that originally VMS did not have a
    Control access bit.  With the addition of ACLs in VMS V4.0, the Control 
    access bit was added and SYS$CHKPRO accepts this bit in the 2 word of
    CHP$_PROT.
    
    Since bytes 4 through 15 (aka words 2-7) are undefined and reserved for
    future use, it is not necessary to specify these bits.  If any of the
    words (current or future) are not specified it is defaulted as allowing
    access for whatever bits are not supplied.  This is no problem for the
    future bits since they don't have any associated value yet.  However,
    you should specify the first 2 words since the first word defines the
    bits most people are familiar with (S:RWED, O:RWED, G:RWED, W:RWED). 
    The second word specifies the S:Control, O:Control, G:Control, W:Control
    bits and if the second isn't specified, then SYS$CHKPRO will grant all
    classes of user Control access to the file (or object your protecting). 
    Under typicaly VMS, only System and Owner get Control access to a file,
    so a value of 0x1100 is a suggested constant for files.  A constant is
    suggested because files don't have SOGW Control access bits stored in
    their file header.  If you don't want to automatically grant the owner
    and/or System users Control access for your object you can use some
    other values, or if you object stores the Control access rights for
    SOGW, then plug those values in here (remember a bit set means no
    access). 
    
    Although I said above that all but 5 bits of the System, Owner, Group,
    World columns are for future use, this is not strictly true.  Since
    SYS$CHKPRO doesn't really know what Read, Write, Execute, Delete, and
    Control really are, it just compairs the CHP$_ACCESS bits against the
    CHP$_PROT bits and if there is a match up, then SYS$CHKPRO says the
    user has that access right.  This means you can define your own kinds of
    access other then RWEDC which SYS$CHKPRO will check for you. 
    
-----------------------------------------------------------------------------*/
{
    unsigned long status;
    short ret_len;
    int j;
    short prot[2];
    struct itmlst items[11];
    
/*---------------------------------------------------------------------------*/

    j = 0;
    if (acl_len) 
    {
        items[j].buflen = acl_len;
        items[j].itmcod = CHP$_ACL;
        items[j].bufadr = acl;
        items[j++].retlen = &ret_len;
    }

    if (rights_list) 
    {
        items[j].buflen = num_rights * 8;
        items[j].itmcod = CHP$_RIGHTS;
        items[j].bufadr = rights_list;
        items[j++].retlen = &ret_len;
    }

    if (addrights) 
    {
        items[j].buflen = 8;
        items[j].itmcod = CHP$_ADDRIGHTS;
        items[j].bufadr = addrights;
        items[j++].retlen = &ret_len;
    }

    if (priv) 
    {
        items[j].buflen = 8;
        items[j].itmcod = CHP$_PRIV;
        items[j].bufadr = priv;
        items[j++].retlen = &ret_len;
    }

    items[j].buflen = 4;
    items[j].itmcod = CHP$_OWNER;
    items[j].bufadr = &file_uic;
    items[j++].retlen = &ret_len;

    prot[0] = prot_code;        /* file SOGW bits */
    prot[1] = 0x1100;           /* constant for Control (W, G, O:c, S:c) */
    items[j].buflen = 4;
    items[j].itmcod = CHP$_PROT;
    items[j].bufadr = &prot;
    items[j++].retlen = &ret_len;

    items[j].buflen = 4;
    items[j].itmcod = CHP$_ACCESS;
    items[j].bufadr = &access_mode;
    items[j++].retlen = &ret_len;

    items[j].buflen = 0;
    items[j].itmcod = 0;
        
    status = sys$chkpro(items);
    if (status == SS$_NORMAL)
    {
        return(1);
    }
    else
    {

#ifdef DEBUG
        fprintf( stderr, "verify_access: sys$chkpro() no access\n" );
        lib$signal( (status & ~0x7) | 0x2 );
#endif
            return(0);
    }
}




main( int ac, char **av )
/*---------------------------------------------------------------------------
    usage:
    
        CHKPRO :== $disk:[dir]CHKPRO.EXE
        CHKPRO username file access [access ...]
    
    for example:
    
        CHKPRO harris sys$login:login.com read write control
        CHKPRO harris sys$login:login.com read control execute
        CHKPRO harris sys$login:login.com delete
        etc...
        
    For full tesing, this code should be modified to check for more then just
    ARM$M_READ access.  The values allowed are, ARM$M_READ, ARM$M_WRITE,
    ARM$M_EXECUTE, ARM$M_DELETE, and ARM$M_CONTROL.  These values should be
    OR'ed together.  This value is eventually passed to $CHKPRO in the
    CHP$_ACCESS item list argument. 
-----------------------------------------------------------------------------*/
{
    unsigned long status;
    unsigned long *user_rights;
    unsigned long user_privs[2];
    unsigned long file_uic;
    unsigned long *file_acl;
    unsigned short file_acllen;
    unsigned short file_protection;
    int user_rightslen;
    
    static int m_tbl[] = {
        ARM$M_READ,
        ARM$M_WRITE,
        ARM$M_EXECUTE,
        ARM$M_DELETE,
        ARM$M_CONTROL
    };
    static char *c_tbl[] = {
        "read",
        "write",
        "execute",
        "delete",
        "control",
        NULL
    };
    char **p;
    int j;
    int k;
    char c_access[64];
    int m_access;
    
/*---------------------------------------------------------------------------*/
/*
  See if we have all the arguments we need.
*/
    if ( ac < 4 )
    {
        usage();
    }
/*
  Parse the 3rd through nth arguments and build the access mask to be used by
  $CHKPRO
*/
    c_access[0] = '\0';
    m_access = 0;
    for( k=3; k < ac; k++ )
    {
        status = 0;
        for( j=0, p=c_tbl; *p != NULL; p++, j++ )
        {
            if ( strncmp( *p, av[k], strlen(av[k]) ) == 0 )
            {
                m_access |= m_tbl[j];
                if ( c_access[0] != '\0' )
                    strcat( c_access, "/" );
                strcat( c_access, *p );
                status = 1;
                break;
            }
        }
        if ( !status )
            usage();
    }
/*
  In a production program, this should be done just once for a user
  connection.  If you do it everytime you are going to check whether a user
  can access a file, it will be just as bad for performance as calling
  $CHECK_ACCESS.
*/    
    user_rights = get_users_uaf_rights( av[1], &user_rightslen, user_privs );
/*
  You need to get this information each time you want to check if a user has
  access to the file *AND* you suspect that the file permissions have changed
  behind your back.  In general I wouldn't suggest keeping this information
  cached for more then a few minutes tops. 
  
  A modification of this routine would be to open the file the way the user has
  requested, and then check to see if he has access.  This way, if the user
  does have access, you don't have to open the file a second time.  If the user
  doesn't have access then this should be the exception case and you would have
  done no more work (don't forget to close the file however :-)
  
  One more comment about this routine.  It uses RMS to get the files access
  rules.  If you are trying for the last ounce of performance, then you want to
  switch to using the XQP interface.  This is not for the faint of heart (in
  fact it is a down right pain, but there are layered products that need to do
  this because RMS adds too much overhead for what they need to do).  I only
  mention this because you have choosen $CHKPRO over $CHECK_ACCESS which
  I'm assuming you are doing because you need better performance (I could be
  wrong :-)
*/
    file_acl = get_file_access_rules( av[2], &file_acllen, 
        &file_uic, &file_protection );
/*
  See if the user has access to the file.
*/
    status = verify_access( file_uic, 
               m_access,
               file_acl, 
               file_acllen, 
               file_protection, 
               user_privs, 
               user_rights,
               user_rightslen, 
               0 
             );
/*
*/
#ifdef DEBUG
    if ( status )
    {
        printf( "%s %s access by %s allowed\n", av[2], c_access, av[1] );
    }
    else
    {
        printf( "%s %s access by %s NOT allowed\n", av[2], c_access, av[1] );
    }
#endif /* DEBUG */

    if ( status )
    {
        return( 1 );
    }
    else
    {
        return( RMS$_PRV | 0x10000000 );
    }
}



usage()
{
#define FP(_s) fprintf( stderr, "%s\n", _s )

    FP( "Usage: CHKPRO :== $disk:[dir]chkpro.exe" );
    FP( "       CHKPRO username filename access [access ...]" );
    FP( "" );
    FP( "       If access is denied, then RMS$_PRV will be the exit status." );
    FP( "       It will be a silent exit.  There will be no error message" );
    FP( "" );
    FP( "       CHKPRO fred   login.com read write control" );
    FP( "       CHKPRO george login.com read control execute" );
    FP( "       CHKPRO harry  login.com delete" );
    FP( "       etc..." );
    
    exit(0x10000002);
}
