CHRIS@YMIR.BITNET (Chris Yoder) (04/26/88)
[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 ``<node>_ACCESS'' where the string <node> 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 ``<node>_ACCESS'' identifier. The precedence of identifiers is: 1) Users holding ``<node>_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 ``<node>_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 <node-name>_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 -------