Path: utzoo!utgpu!water!watmath!clyde!bellcore!decvax!decwrl!ucbvax!YMIR.BITNET!CHRIS
From: CHRIS@YMIR.BITNET (Chris Yoder)
Newsgroups: comp.os.vms
Subject: Managing cluster users with identifiers.
Message-ID: <8805121830.AA00909@ucbvax.Berkeley.EDU>
Date: 25 Apr 88 22:47:00 GMT
Sender: daemon@ucbvax.BERKELEY.EDU
Organization: The Internet
Lines: 553
[bug food]
Well, here I go again, trying to submit to INFO-VAX from BITNET. I'm
beginning to think that this is a fruitless exercise because I never see
anything come back...
Included is NODECHECK, a utility that allows cluster managers to control
who logs onto what nodes in the cluster by means of identifiers. Have fun, and
send me any comments or bug fixes.
-- Chris Yoder
Harvey Mudd College Bitnet ----- Chris@Ymir.Bitnet
$!---------------------------- Cut here ----------------------------------------
$ show default
$ check_sum = 958622119
$ write sys$output "Creating 000README.1ST"
$ create 000README.1ST
$ DECK/DOLLARS="$*$*EOD*$*$"
NODECHECK is a utility which gives VAX Cluster managers the ability to
allow and disallow the users of that cluster to log into a given node.
NODECHECK is entirely driven by the identifiers that the user holds and needs
no privileges. Some system setup is required.
NODECHECK unconditionally allows users onto a node if they hold an
identifier of the form ``_ACCESS'' where the string is equal to
the SYSGEN parameter SCSNODE (this should also be the DECnet node name). It
will conditionally allow users holding an identifier in the list of identifiers
specified by the /ALLOW qualifier to log in. Finally, users holding
identifiers in the list of identifiers specified by the /DISALLOW qualifier
will not be allowed to log in.
The /ALLOW and /DISALLOW qualifiers are conditional in that the user is
allowed to log in if they have an identifier on the /ALLOW list and they don't
have an identifier on the /DISALLOW list. If they hold an identifier on both
lists, then the conflict is resolved by whether the user holds the
``_ACCESS'' identifier. The precedence of identifiers is:
1) Users holding ``_ACCESS'' identifiers always get on.
2) Users only holding one or more identifiers on the /ALLOW list get on iff
they do not hold any identifiers on the /DISALLOW list.
3) Users only holding one or more identifiers on the /DISALLOW list are not
allowed to log on.
4) Users who do not hold either the ``_ACCESS'' identifier or an
identifier on the /ALLOW list are not allowed to log on.
SYSTEM SETUP: (The utterly simplified version.)
1) PAScal and LINk NODE_ACCESS
2) Move NODE_ACCESS.EXE to wherever you want it to live.
(I use the system logical PRIV: to point to a directory where all
system-type public-domain and locally written software live).
3) Edit NODECHECK.CLD so that the image line of the verb NODECHECK points to
that directory.
4) Add the NODECHECK CLD file into the appropriate DCLTABLES.
(In an environment where all systems share the same DCLTABLES, the
command for this is:
$ SET COMMAND /TABLE=SYS$LIBRARY:DCLTABLES -
/OUTPUT=SYS$COMMON:[SYSLIB]DCLTABLES NODECHECK
)
5) Reinstall DCLTABLES on each node in the cluster.
(On each node execute the command:
$ MCR INSTALL
INSTALL> SYS$LIBRARY:DCLTABLES/REPLACE
)
6) Add the appropriate NODECHECK command to SYLOGIN.COM.
EXAMPLES:
1) Say Joe Random Prof owned a workstation named RANDOM and he didn't want
any STUDENTS to log on to his workstation but would allow all
SYSTEM_MANAGEMENT and FACULTY on. Assuming that all students hold the
identifier STUDENTS, all faculty hold the identifier FACULTY, and all
system management types hold the identifier SYSTEM_MANAGEMENT, the
command in SYLOGIN.COM would be:
$ NODECHECK/OWNER="Joe Random Prof"/ALLOW=(SYSTEM_MANAGEMENT,FACULTY)-
/DISALLOW=STUDENTS
If user held both the STUDENT and SYSTEM_MANAGEMENT identifier, but not
the RANDOM_ACCESS identifier, that student could not log on.
2) If it is desired to allow most users onto the system and only exclude
those who hold identifiers on the /DISALLOW list from a given node,
then the system manager may grant an identifier to all users, say
"USER" and include the USER identifier in the /ALLOW list. The above
command would change to:
$ NODECHECK/OWNER="Joe Random Prof"/ALLOW=(SYSTEM_MANAGEMENT,FACULTY,USER)-
/DISALLOW=STUDENTS
This would allow all users who do not hold the STUDENT identifier to log
onto the node RANDOM. Of course, users with the STUDENT identifier who
also hold the RANDOM_ACCESS identifier are allowed to log in.
$*$*EOD*$*$
$ checksum 000README.1ST
$ if checksum$checksum .ne. check_sum then -
$ write sys$output "Checksum failed, file probably corrupted"
$ check_sum = 1265006991
$ write sys$output "Creating NODECHECK.CLD"
$ create NODECHECK.CLD
$ DECK/DOLLARS="$*$*EOD*$*$"
!
! Command definition for NODECHECK utility
!
define syntax DEBUG
image pub$root:[source.nodecheck]node_access
qualifier OWNER
value (REQUIRED)
qualifier ALLOW
value (LIST,REQUIRED)
qualifier DISALLOW
value (LIST,REQUIRED)
define verb NODECHECK
image priv:node_access
qualifier DEBUG
syntax = DEBUG
qualifier OWNER
value (REQUIRED)
qualifier ALLOW
value (LIST,REQUIRED)
qualifier DISALLOW
value (LIST,REQUIRED)
$*$*EOD*$*$
$ checksum NODECHECK.CLD
$ if checksum$checksum .ne. check_sum then -
$ write sys$output "Checksum failed, file probably corrupted"
$ check_sum = 994520805
$ write sys$output "Creating NODE_ACCESS.PAS"
$ create NODE_ACCESS.PAS
$ DECK/DOLLARS="$*$*EOD*$*$"
[Ident ('1.0'),
Inherit ('SYS$Library:Starlet')]
Program Node_Access (Output);
{
This program will check to see if the user has the identifier
_ACCESS. If the user has it, nothing is output. If the
user does not have this identifier, then the user is informed that
he does not have access to the particular node and the process is
deleted. This program is intended to be run from SYLOGIN.COM.
Written by: Chris Yoder
for: Harvey Mudd College
Mathematics Department Computing Laboratory
on: March 14, 1988
}
CONST
{
CLI constants.
}
CLI$_ABSENT = %x'000381F0';
CLI$_COMMA = %x'0003FD39';
CLI$_CONCAT = %x'0003FD29';
CLI$_DEFAULTED = %x'0003FD21';
CLI$_LOCNEG = %x'00038230';
CLI$_LOCPRES = %x'0003FD31';
CLI$_NEGATED = %x'000381F8';
CLI$_PRESENT = %x'0003FD19';
TYPE
Word = [WORD] -32768..32767;
UnsignedWord = [WORD] 0..65535;
String80 = Varying[80] of CHAR;
{TYPE Definitions
Word ------------------- A type to hold a word.
UnsignedWord ----------- An unsigned word type.
String80 --------------- A string 80 characters long.
}
VAR
Node : String80;
Identifier : String80 := '_ACCESS';
Owner : String80 := 'your system management';
Explicit_Access : BOOLEAN := FALSE;
User_Has_Access : BOOLEAN := FALSE;
{VAR Definitions
Node ------------------- The node that we are running on.
Identifier ------------- The default identifier to check on.
Owner ------------------ The owner of the machine.
Explicit_Access -------- True for users with the node_ACCESS identifier.
User_Has_Access -------- True if the user can log on to this node.
}
{******************************************************************************}
[EXTERNAL, UNBOUND]
FUNCTION CLI$Present (
EntityDesc : [CLASS_S] Packed Array [ $l1..$u1 : Integer ] Of Char
) : Integer; External;
{******************************************************************************}
[EXTERNAL, UNBOUND]
FUNCTION CLI$Get_Value (
EntityDesc : [CLASS_S] Packed Array [ $l1..$u1 : Integer ] Of Char;
RetDesc : [CLASS_S] Packed Array [ $l2..$u2 : Integer ] Of Char;
VAR RetLength : [UNSAFE] UnsignedWord := %Immed 0
) : Integer; External;
{******************************************************************************}
FUNCTION LIB$Stop
(%immed condition_value1 : [LIST, UNSAFE] UNSIGNED )
: UNSIGNED;
EXTERNAL;
{******************************************************************************}
FUNCTION LIB$GetJPI
( item_code : INTEGER;
%ref process_id : UNSIGNED := %immed 0;
process_name : VARYING [$len3] OF CHAR := %immed 0;
%ref out_value : UNSIGNED := %immed 0;
VAR out_string : VARYING [$len5] OF CHAR := %immed 0;
VAR out_len : Word := %immed 0 )
: UNSIGNED;
EXTERNAL;
{******************************************************************************}
Procedure Check (Status_Code : Unsigned);
{
This procedure will check the status code returned from a system
call. If it is OK, then we continue on, otherwise we signal the error
and abort the program.
}
BEGIN { Procedure Check }
IF NOT ODD (Status_Code)
THEN LIB$Stop (Status_Code);
END; { Procedure Check }
{******************************************************************************}
Function User_Has_Identifier (Identifier : String80) : BOOLEAN;
TYPE
Holder_Type = [QUAD,UNSAFE] RECORD
UIC : Unsigned;
Reserved : Unsigned;
END;
{ TYPE Definitions
Holder_Type --- Put the UIC into a Quadword to pass to $Find_Held
}
VAR
Hold_Result : Boolean;
UIC : Unsigned;
Holder : Holder_Type;
Bin_Id : [Volatile] Unsigned;
Check_Id : [Volatile] Unsigned;
Context : [Volatile] Unsigned;
Ret_Status : Unsigned;
{ VAR Definitions
Identifier ---- The identifier that we want to check on.
Hold_Result --- Hold the result of the function before we return.
UIC ----------- The UIC of the user running the program.
Holder -------- Who's UIC we're checking for.
Bin_Id -------- The Binary form of the ASCII identifier that we were
passed.
Check_Id ------ The current id that we are checking.
Context ------- The context variable for $Find_Held
Ret_Status ---- Hold the return status of a call to a system routine.
}
BEGIN { Function User_Has_Identifier }
{ We haven't found the identifier, so this function is set to FALSE until
we do. }
Hold_Result := FALSE;
{ Convert the identifier that has been passed here from ASCii format TO
binary IDentifier format. }
Ret_Status := $AscToId (Identifier,Bin_Id,);
{ Be graceful about an identifier not existing and don't bomb out. }
IF (Ret_Status = SS$_NoSuchId)
THEN Writeln ('Identifier ',Identifier,' does not exist.')
ELSE
BEGIN
{ Do the check here so that we don't blow the user out of the program
if the identifier doesn't exist. }
Check (Ret_Status);
{ Initialize a couple of things before we start calling $Find_Held. }
{ Initialize the context variable for $Find_Held }
Context := 0;
{ Get the UIC of the current process and convert it into something
that $Find_Held will like. }
Check (LIB$GetJPI (JPI$_UIC,,,UIC,,));
Holder.UIC := UIC;
Holder.Reserved := 0;
{ Call $Find_Held repeatedly. Each time returns an Identifier found
in the rights_list database until we run out of identifiers for
the current UIC. When this happens, the status SS$_NoSuchId
is returned. }
REPEAT { UNTIL we find the identifier or we run out of identifiers
to try. }
Ret_Status := $Find_Held (Holder,Check_Id,,Context);
{ Be graceful about a status that we know will come up. }
IF (Ret_Status <> SS$_NoSuchId)
THEN Check (Ret_Status);
{ Test to see if the two binary formats of the identifier are the
same. If we match, then we've found it. Otherwise continue. }
Hold_Result := (Check_Id = Bin_Id);
UNTIL Hold_Result OR (Ret_Status = SS$_NoSuchId);
{ Clean up any channels assigned to the rightslist database only if
we find the identifier. When SS$_NoSuchId is returned
$Find_Held does this automatically. }
IF Hold_Result
THEN Check ($Finish_RDB (Context));
END; { IF (Ret_Status = SS$_NoSuchId) ... ELSE (i.e. the identifier
exists.) }
{ Send back the result. }
User_Has_Identifier := Hold_Result;
END; { Function User_Has_Identifier }
{******************************************************************************}
Function User_On_List (Qualifier : String80) : BOOLEAN;
{
This Function will check to see if the user is on the qualifier
list. Since right now both an ALLOW and a DISALLOW set may be
specified, just return whether or not the user holds one of the
Identifiers on the list.
}
TYPE
Access_List_Ptr = ^Access_List_Record;
Access_List_Record = RECORD
Identifier : String80;
Next : Access_List_Ptr;
END; { RECORD Access_List_Record;
{TYPE Definitions
Access_List_Ptr ------ A pointer to a list of access identifiers
Access_List_Record --- A record for a linked list of access id's
}
VAR
Access_Identifiers : Access_List_Ptr;
Hold_Ptr : Access_List_Ptr;
Hold_Result : BOOLEAN;
Ret_Status : INTEGER;
{VAR Definitions
Access_Identifiers --- Identifiers that allow access.
Hold_Ptr ------------- A temporary pointer.
Hold_Result ---------- The result of the function 'till it gets passed.
Ret_Status ----------- Hold a return status.
}
BEGIN { Function User_On_List }
Hold_Result := False;
{ Build the list of identifiers. }
Hold_Ptr := NIL;
Access_Identifiers := NIL;
NEW (Hold_Ptr);
Hold_Ptr^.Next := NIL;
Hold_Ptr^.Identifier := '';
IF ((CLI$Present (Qualifier) = CLI$_Present) OR
(CLI$Present (Qualifier) = CLI$_Defaulted))
THEN
REPEAT
Ret_Status := CLI$Get_Value ( Qualifier,
Hold_Ptr^.Identifier.Body,
Hold_Ptr^.Identifier.Length);
IF (Access_Identifiers = NIL)
THEN Access_Identifiers := Hold_Ptr
ELSE
BEGIN
Hold_Ptr^.Next := Access_Identifiers;
Access_Identifiers := Hold_Ptr;
END;
Hold_Ptr := NIL;
NEW (Hold_Ptr);
Hold_Ptr^.Next := NIL;
Hold_Ptr^.Identifier := '';
UNTIL (Ret_Status = SS$_Normal);
WHILE NOT Hold_Result AND (Access_Identifiers <> NIL) DO
BEGIN
Hold_Result := User_Has_Identifier (Access_Identifiers^.Identifier);
Hold_Ptr := Access_Identifiers;
Access_Identifiers := Access_Identifiers^.Next;
Hold_Ptr^.Next := NIL;
DISPOSE (Hold_Ptr);
END;
User_On_List := Hold_Result;
END; { Function User_On_List }
{******************************************************************************}
Procedure Initialize;
{
Initialize the Node name and the Identifier that goes with it.
}
TYPE
IO_Status_Block_Type = RECORD
IO_Stat, Count : UnsignedWord;
Dev_Info : INTEGER;
END; { RECORD IO_Status_Block_Type }
SYI_Info_Block_Type = RECORD
Buffer_Length : UnsignedWord;
Item_Code : UnsignedWord;
Buffer_Address : Unsigned;
Ret_Length_Address : Unsigned;
END; { RECORD SYI_Info_Block_Type }
{TYPE Definitions
IO_Status_Block_Type --- A standard IO status block.
SYI_Info_Block_Type ---- Hold a block to pass to $GETSYI.
}
VAR
First_Space : INTEGER;
SYI_Info : ARRAY [1..2] OF SYI_Info_Block_Type;
IO_Stat : IO_Status_Block_Type;
{VAR Definitions
First_Space ------------ The first space in the node string.
SYI_Info --------------- The item list that we pass to $GETSYI
IO_Stat ---------------- Hold the IO status of a call
}
BEGIN { Procedure Initialize }
Node := '';
Node := PAD (Node,' ',80);
{ Get the node name. }
SYI_Info[1].Buffer_Length := 80;
SYI_Info[1].Item_Code := SYI$_SCSNODE;
SYI_Info[1].Buffer_Address := (iaddress (Node.Body)) :: Integer;
SYI_Info[1].Ret_Length_Address := (iaddress (Node.Length)) :: Integer;
SYI_Info[2].Buffer_Length := 0;
SYI_Info[2].Item_Code := 0;
SYI_Info[2].Buffer_Address := 0;
SYI_Info[2].Ret_Length_Address := 0;
IO_Stat.IO_Stat := 0;
IO_Stat.Count := 0;
IO_Stat.Dev_Info := 0;
Check ($GetSyIW (,,,SYI_Info,IO_Stat,,));
Check (IO_Stat.IO_Stat);
First_Space := INDEX (Node,' ');
Node := SUBSTR (Node,1,First_Space-1);
{ Make up the Identifier. }
Identifier := Node + Identifier;
END; { Procedure Initialize }
{******************************************************************************}
BEGIN { Program Node_Access }
Initialize;
{ Check for Explicit Access to this node. }
Explicit_Access := User_Has_Identifier (Identifier);
User_Has_Access := Explicit_Access;
{ Check to see if the user has one of the Identifiers on the ALLOW list.
For the sake of being efficient, we only check if we have to. }
IF NOT User_Has_Access
THEN User_Has_Access := User_On_List ('ALLOW');
{ Check to see if the user has one of the Identifiers on the DISALLOW list.
For the sake of being efficient, we only check if we have to. }
IF User_Has_Access AND NOT Explicit_Access
THEN User_Has_Access := NOT User_On_List ('DISALLOW');
{ Kick the user off of the system if they aren't allowed on. }
IF NOT User_Has_Access AND NOT Explicit_Access
THEN
BEGIN
{ Grab the owner information (if present) }
IF ((CLI$Present ('OWNER') = CLI$_Present) OR
(CLI$Present ('OWNER') = CLI$_Defaulted))
THEN Check (CLI$Get_Value ('OWNER',Owner.Body,Owner.Length));
Writeln;
Writeln ('You are not authorized to use node ',Node,'.');
Writeln ('Please see ',Owner,' for authorization.');
Writeln;
$DelPrc(,);
END;
END. { Program Node_Access }
$*$*EOD*$*$
$ checksum NODE_ACCESS.PAS
$ if checksum$checksum .ne. check_sum then -
$ write sys$output "Checksum failed, file probably corrupted"
$ exit
-------