   dim Version$/"LOGOFF V1.0d (C) 1985 Software Dynamics"/
!  LOGOFF is a program run by SDOS/MT at boot time, or by a user
!  when he is through using an SDOS system.  Its primary purpose
!  is to prevent unauthorized users from gaining access to an SDOS system.
!  This is useful for systems used in high-traffic areas, or
!  on systems which have dial-in ports.
!
!  Revision History:
!
!      V1.0a   3/15/85       First Implementation
!      V1.0b   7/28/85       Modified to lowercase ID/PASSWORD entered
!                               before comparing against database.
!                            Print <CR> after password entered
!                               to make for neat display
!      V1.0c   10/5/85       Accepts TIME if not properly set.
!      V1.0d   10/11/85      Attempts to "DISMOUNT DISK:" when
!                            user logs off to help ensure that disk
!                            has heads parked when last user logs out.
!                            Support password no longer part of database.
!                            Added user named "MANAGER" whose password
!                            logs one into the manager "support" logic.
!                            This cheap trick keeps Manager's password hidden.
!                            Erase screen at exit of manager logic so
!                            user info on screen is not accidentally
!                            seen by others.
!                            Notifies logging-out user if any other users
!                            have files open (cheap "shutdown" test.
!                            LOGOFF.DATA creation password changed to "zap"
!
!  DOCUMENTATION:
!
!  The program has two modes of use: NORMAL and SUPPORT.
!  Normal mode is the default, and occurs when the program is started
!  with no parameters.  In Normal mode, the program merely waits
!  for a keyboard entry matching a person's ID, and then asks
!  for a password.  If the ID and the password match a User profile
!  data base, then the user is allowed access to the computer.
!  (If one desires anybody to use the system, one simply adds a user
!  with the ID "ANONYMOUS" and an empty password).
!  A failed match causes denial of access to the computer.
!  Failed attempts get recorded in a log file so
!  that attempts to crack a system can be traced.
!  A successful attempt is called a LOGIN, and the ID of the logged in person
!  is timestamped and recorded in the LOGOFF.DATA file.
!  The file LOGOFF.NEWS is printed so that system messages can
!  be seen by all users.
!  Initial access to the computer causes a particular program, dependent
!  on the user ID, to fetched; normally, this is DEFAULTPROGRAM,
!  but it may be something else if one wishes to restrict the user
!  to running a particular program.  Normal mode runs KILLPROOF, so
!  it cannot be aborted by a conventional user.  KILLPROOF mode is
!  removed if the user profile so states.
!
!  Support mode is entered when the LOGOFF program detects a login
!  attempt with user id of MANAGER that has the correct password.
!  In support mode, one can add new ID and profile information,
!  delete a user ID and profile information, change a
!  user's password or profile (by deleting and re-inserting the ID);
!  or one can adjust the LOGOFF.DATA file.  These functions cannot
!  be performed by users not in support mode, as the LOGOFF.DATA file
!  has its contents stored in an encrypted (I wish!) format.
!  Support mode is also entered the first time the program is operated
!  to allow the manager's account to be inserted into the database.
!
!  LOGOFF.DATA file format:
!
!        16 Bytes    LOGOFF data file interlock object ID.
!        64 Bytes    System ID message.  Printed at each login attempt.
!        6 Bytes     Number of user records
!        57 Bytes    User Record.  Format as follows:
!                    16 Bytes:  User ID, lowercase, blank padded
!                    16 Bytes:  User password, lowercase, blank padded
!                    1 Byte:  KILLENABLE flag
!                    24 Bytes: Name of program to run on LOGIN
!
!        Ascii line: User LOGIN record
!                    8 bytes: "LOGIN" "LOGOUT" or "FAILED"
!                    ~15 Bytes: MM/DD/YY HH:MM (time of login attempt)
!                    16 Bytes: User ID, blank padded
!                    16 Bytes: User password if login attempt failed
!
Dim LogoffInterlock$(16),SystemID$(64),NumberOfUserIDs

Dim UserID$(16),UserPassword$(16),KillEnableFlag$(1),UserInitialProgram$(24)
Dim UserRecordSize/57/,UserRecordBase/86/

Dim EnteredID$(16),EnteredPassword$(16),...
&   EnteredKillEnableFlag$(1),EnteredUserInitialProgram$(24)

Dim UserRecord$(57),OldUserRecord$(57)

Dim LogoffDataFile$/"LOGOFF.DATA"/
!
Dim Line$(128),STime$(6)
!
Dim CCPositionToEnd$/:E,4,0,:13/
Dim CCSetFileSize$/:E,4,0,:12/
Dim CCSetFileProt$/:E,8,0,:11/,WriteProtect$/:40/,NoProtect$/:0/
Dim CCDismountDisk$/:E,4,0,:11/

Dim SyscallKillProof$/:16,2/,SyscallKillEnable$/:17,2/

Dim SyscallCloseLog$/:8,2/

Dim SyscallDelayThirtySeconds$/:1C,4,:08,00/

Dim CCEchoEnable$/:E,4,0,:10/,CCNoEcho$/:E,4,0,:11/

Dim ILCreateInterlock$/:1B,14,0,0/
Dim ILLockInterlock$/:1B,14,0,3/,ILReleaseInterlock$/:1B,14,0,4/

Subroutine LockDataBase
   ! Subroutine to set things up so it is safe to read Database
   ! Also get Database parameters
   Call OpenLogoffData \ ! on channel #1
   Return Subroutine
End

Subroutine SetupToModifyDataBase
   ! Subroutine to set things up so it is safe to modify Database
   ! Also get Database parameters
   Call OpenLogoffData \ ! on channel #1
   Syscall #1,CCSetFileProt$,NoProtect$ \ ! Remove Write protect from file
   Return Subroutine
End

Subroutine ReleaseDataBase
   ! Subroutine to clean up after database access
   Syscall #1,CCSetFileProt$,WriteProtect$ \ ! Add Write protect to file
   Syscall #1,ILReleaseInterlock$,LogoffInterlock$
   Close #1
   Return Subroutine
End

Subroutine OpenLogoffData
   ! Subroutine to open and lock the LOGOFF.DATA file
   Open #1,LogoffDataFile$ \ ! Assert: this file exists
!!!!! I think the error recovery for non-existent db s/b here!
   Read #1,LogoffInterlock$
   Syscall ILLockInterlock$,LogoffInterlock$ \ ! Wait for safe access
   Read #1,SystemID$,NumberOfUserIDs
!D! Print "|";SystemID$;"|";NumberOfUserIDs
   Return Subroutine
End
!^L
DEF MSB(X1)=INT(X1/256)
DEF LSB(X2)=INT(X2-256*MSB(X2))

Def NextField(NextField$,NextFieldDelimiter$)
    For i=1 to len(NextField$)
        If NextField$(i,1)<>" " Then Exit i \ ! Skip leading blanks
    Next i
    For i=i to len(NextField$)
        If NextField$(i,1)<"0" or NextField$(i,1)>"9" Then Exit i
    Next i
    If i>len(NextField$) and NextFieldDelimiter$=""
    Then
        Let i0=val(NextField$)
        Let NextField$="" \ ! Remove field from front
        Return i0
    ElseIf i<=len(NextField$) and NextField$(i,1)=NextFieldDelimiter$
    Then
        Let i0=val(NextField$)
        Let NextField$=Right$(NextField$,i+1) \ ! Remove field from front
        Return i0
    Else Error 102 \ ! Bad command format
End

Def BCD(BCDarg)
    Let i=int(BCDarg/10)
    Return i*16+(BCDarg-i*10)
End

Subroutine EnsureTimeIsSet
   ! Subroutine to make sure time is set correctly
   Open #2,"CLOCK:"
   Repeat
       Read #2,STime$
       If STime$[4]<>0 and STime$[5]<>0
       Then Close #2\Return Subroutine
       ! Allow operator to enter time/date
       Print "Time is not set, please enter it:"
       Print ".TIME hh:mm mm/dd/yy";
       For i=1 to 14 do print chr$(:8); \ ! Backspace cursor to HH:MM...
       If Error When
           Input "" Line$
           Time=NextField(Line$,":")*60^3+NextField(Line$," ")*60^2
           Len(STime$)=6
           STime$[3]=LSB(Time)
           STime$[2]=LSB(MSB(Time))
           STime$[1]=MSB(MSB(Time))
           Stime$[5]=BCD(NextField(Line$,"/"))
           Stime$[4]=BCD(NextField(Line$,"/"))
           Stime$[6]=BCD(NextField(Line$,""))
!D!        Print "Attempting to set time."
           Write #2,STime$
       Then
           Print "Invalid time/date."
       Fi
   End
End
!^L
! **** Main program starts here ***
LOGOFF: ! Control transfers here when LOGOFF is started.
   If Col(0)>1
   Then
       ! Command line parameter was given, fetch it to store in logout record
       Input "" Line$
       Print Version$ \ ! Show version number
   Else
       Line$="" \ ! So something goes into logout record
   Fi
   Print "User logged off at ";time$;" ";date$
   ! Kill off any active DO file (boy, some guys will try anything!)
   If Error When Syscall SyscallCloseLog$
   Then If Err=1032
        Then Close #0\Open #0,"CONSOLE:"
        Else Error \ ! Something sick about log channel
   If Error When Open #1,LogoffDataFile$
   Then
       If Err=1011
       Then
           Print LogoffDataFile$;" file doesn't exist."
           Input "Shall I create it? " Line$
           Let Line$=LowerCase$(Line$)
           If Line$<>"zap"
           Then
               Print "*** Can't log you out: report to system manager. ***"
               Exit
           Fi
           EnsureTimeIsSet
           Create #1,LogoffDataFile$
           len(line$)=16 \ ! Make dummy Interlock$ capability
           Syscall ILCreateInterlock$,Line$,LogoffInterlock$
           Syscall ILLockInterlock$,LogoffInterlock$
           len(SystemID$)=maxlen(SystemID$)
           Input "System ID: " SystemID$[1,len(SystemID$)] \ ! Blank fill
           NumberOfUserIDs=0 \ ! How many IDs we have collected
           ! Store initializing data into data base.
           Write #1,LogoffInterlock$,SystemID$,NumberOfUserIDs
           ReleaseDataBase
           Print LogoffDataFile$;" created."
           Print "Now add special MANAGER user ID."
           Print "Only MANAGER has access to LOGOFF.DATA."
           ! This ensures that database has method for modification
           let line$="add MANAGER"
           Print "LOGOFF>";line$
           Goto AddUserID \ ! Now simply add this user to data base
       Fi
   Fi
   Close #1
!^L
LogoutUser: ! Log this user off the machine.
   Syscall SyscallKillProof$ \ ! Prevent escape via ^C^C
   SetupToModifyDataBase \ ! and fetch SystemID$ for login time banner
   ! Assert: the first user to get here immediately after boot
   ! will be user #0, i.e., the person booting the system.
   ! So we offer him the opportunity to set the time, and then
   ! we proceed.  While we are setting the time, all the other
   ! users are waiting for the data base, and so can't have this problem.
   EnsureTimeIsSet
   Syscall #1,CCPositionToEnd$
   Print #1,"LOGOFF ";date$;" ";time$;" ";line$
   ReleaseDataBase
   Open #1,"DISK:" \ ! Attempt to dismount DISK:
   If Error When Syscall #1,CCDismountDisk$
   Then If Err=1002
        Then Print "Note: There are other users still logged in."
        Else Error \ ! Die if anything but "Can't dismount because file open"
   Close #1

WaitForLOGIN: ...
&  Repeat
       Close #0\Open #0,"CONSOLE:" \ ! In case of ^Z
       Syscall SyscallKillProof$ \ ! Prevent escape via ^C^C
       Print
       Print SystemID$
       Print "at ";time$;" ";date$
       If Error When
           Syscall #0,CCEchoEnable$
           len(EnteredID$)=maxlen(EnteredID$)
           Input "User ID: " EnteredID$[1,len(EnteredID$)]
           Let EnteredID$=lowercase$(EnteredID$)
           Syscall #0,CCNoEcho$
           len(EnteredPassword$)=maxlen(EnteredPassword$)
           Input "Password: " EnteredPassword$[1,len(EnteredPassword$)]
           Print \ ! Because CCNoEcho surpressed echo of <CR>
           Let EnteredPassword$=lowercase$(EnteredPassword$)
       Then
           Print "Illegal User ID/Password. Try again."
       Else
           If Error When
              SetupToModifyDatabase
           Then Print Err \ ! and loop, preventing user from logging in
           Else
               ! Scan thru User records, looking for a match
               For i=1 to NumberOfUserIDs
                   ! Read next user record
                   Read #1,UserID$,UserPassword$,...
&                          KillEnableFlag$,UserInitialProgram$
                   If UserID$=EnteredID$ and UserPassword$=EnteredPassword$
                   Then SuccessfulLogin \ ! found matching ID and password
               Next i
               ! Failed to match any valid user record.
               Print "No such UserID/Password combination, try again in 30 seconds."
               ! Note failed attempt in data file for inspection by manager.
               Syscall #1,CCPositionToEnd$
               Print #1,"Failed LOGIN: ";date$;" ";time$;" ";...
&                       EnteredID$;" ";EnteredPassword$
               ReleaseDataBase
               ! Make repeated attempts to crack the system dreadfully slow.
               Syscall SyscallDelayThirtySeconds$
           Fi
       Fi
   End \ ! Repeat until successful login
!^L
SuccessfulLogin: ! Matching UserID and password have been found.
   ! Record successful login in the data file.
   Syscall #1,CCPositionToEnd$
   Print #1,"LOGIN  ";date$;" ";time$;" ";EnteredID$
   ReleaseDataBase

   Syscall #0,CCEchoEnable$ \ ! which was disabled for the password

   If Error When Open #1,"LOGOFF.NEWS"
   Then
       If Err=1011
       Then ! No news is no news. Don't print anything.
       Else Error \ ! Croak die....
   Else
       Repeat
            ! Print the contents of the NEWS file.
            Input #1,Line$
            If not(EOF(1)) Then Print Line$
       Unless EOF(1) End
       Close #1 \ ! just to be nice
   Fi
   Let len(Line$)=maxlen(EnteredID$) \ ! Construct "MANAGER" User ID
   Line$[1,maxlen(EnteredID$)]="manager"
   If EnteredID$<>Line$
   Then
       ! It is not the manager who is logging in.
       If KillEnableFlag$="Y" Then Syscall SyscallKillEnable$
       Chain UserInitialProgram$ \ ! Pass control to user-specific program
   Fi
   ! Manager is logging in.  Kill-Enable him.
   Syscall SyscallKillEnable$
!^L
PrintSupportModeMenu: ! Print out menu so MANAGER knows what he can do
   Print
   Print "     LOGOFF MANAGER support mode commands"
   Print
   Print "add <USERID>"
   Print "delete <USERID>"
   Print "printuserinfo <FILENAME>"
   Print "printaccesslog <FILENAME>"
   Print "resetaccesslog"
   Print "changesystemid"
   Print "help"
   Print "exit"
   Print
AskCommand: ! Ask user what he wants to do
   Input "LOGOFF>" Line$
   If find(lowercase$(line$),"add ")=1 Then AddUserID
   If find(lowercase$(line$),"delete ")=1 Then DeleteUserID
   If find(lowercase$(line$),"printuserinfo")=1 Then PrintUserInfo
   If find(lowercase$(line$),"printaccesslog")=1 Then PrintAccessLog
   If find(lowercase$(line$),"resetaccesslog")=1 Then ResetAccessLog
   If find(lowercase$(line$),"changesystemid")=1 Then ChangeSystemID
   If find(lowercase$(line$),"help")=1 Then PrintSupportModeMenu
   If find(lowercase$(line$),"exit")=1 Then Print chr$(:c)\Exit
   Print "?? Illegal LOGOFF support command."
   Goto PrintSupportModeMenu
!^L
AddUserID: ! Wants to add another user ID
   let line$=lowercase$(line$)
   let len(EnteredID$)=maxlen(EnteredID$)
   let EnteredID$[1,len(EnteredID$)]=right$(line$,5)
   let len(EnteredPassword$)=maxlen(EnteredPassword$)
   Input "Password: " EnteredPassword$[1,len(EnteredPassword$)]
   let EnteredPassword$=lowercase$(EnteredPassword$)
   Repeat
       ! Ask whether to kill enable after LOGIN or not.
       Input "Kill Enable after login (Y/N)? " EnteredKillEnableFlag$
       EnteredKillEnableFlag$=UpperCase$(EnteredKillEnableFlag$)
   Unless EnteredKillEnableFlag$="Y" or EnteredKillEnableFlag$="N" End
   len(EnteredUserInitialProgram$)=maxlen(EnteredUserInitialProgram$)
   Input "Initial program to run? " ...
&        EnteredUserInitialProgram$[1,len(EnteredUserInitialProgram$)]
   SetupToModifyDataBase
   For i=1 to NumberOfUserIDs
       ! Read next user record
       Read #1,UserID$,UserPassword$,...
&              KillEnableFlag$,UserInitialProgram$
       If UserID$=EnteredID$
       Then
           Print "User ID already exists, can't add."
           ReleaseDataBase
           Goto AskCommand
       Fi
   Next i
   Read #1@(i-1)*UserRecordSize+UserRecordBase,OldUserRecord$ \ ! make room for new record
   ! Now shuffle tail end of file up one User record's worth
   Repeat
      i=i+1
      Read #1@(i-1)*UserRecordSize+UserRecordBase,UserRecord$ \ ! make room for OldUserRecord$
      Write #1@(i-1)*UserRecordSize+UserRecordBase,OldUserRecord$
      OldUserRecord$=UserRecord$
   Unless len(UserRecord$)=0 End
   ! Add new UserID record
   Write #1@NumberOfUserIDs*UserRecordSize+UserRecordBase,...
&           EnteredID$,EnteredPassword$,...
&           EnteredKillEnableFlag$,EnteredUserInitialProgram$
   NumberOfUserIDs=NumberOfUserIDs+1 \ ! bump # User ID records and record
   Write #1@0,LogoffInterlock$,SystemID$,NumberOfUserIDs
   ReleaseDataBase
   Goto AskCommand
!^L
DeleteUserID: ! Wants to remove a user ID
   let line$=lowercase$(line$)
   let len(EnteredID$)=maxlen(EnteredID$)
   let EnteredID$[1,len(EnteredID$)]=right$(line$,8)
   SetupToModifyDataBase
   For i=1 to NumberOfUserIDs
       ! Read next user record
       Read #1,UserID$,UserPassword$,...
&              KillEnableFlag$,UserInitialProgram$
       If UserID$=EnteredID$
       Then
           ! Found ID of record to delete
           ! Replace record to be deleted with last User Record
           Read #1@(NumberOfUserIDs-1)*UserRecordSize+UserRecordBase,UserRecord$
           Write #1@(i-1)*UserRecordSize+UserRecordBase,UserRecord$
           NumberOfUserIDs=NumberOfUserIDs-1 \ ! shrink record count
           Write #1@0,LogoffInterlock$,SystemID$,NumberOfUserIDs
           ! Now shuffle end of file down by one record
           i=NumberOfUserIDs+1 \ ! where to start shuffling
           Repeat
               Read #1@i*UserRecordSize+UserRecordBase,UserRecord$
               Write #1@(i-1)*UserRecordSize+UserRecordBase,UserRecord$
               If len(UserRecord$)<>maxlen(UserRecord$)
               Then
                   ! All done shuffling
                   Syscall #1,CCSetFileSize$ \ ! Chop file off here
                   ReleaseDataBase
                   Goto AskCommand
               Fi
               i=i+1
           End
       Fi
   Next i
   Print "No such user ID."
   ReleaseDataBase
   Goto AskCommand
!^L
PrintUserInfo: ! Wants to print out all existing user IDs
   if len(line$)>13
   then Create #2,right$(line$,15)
   else Create #2,"console:"
   LockDataBase
   Print #2,"    User ID         Password     KE     Initial Program"
   !        "123456789112345678921234567893123456789412345678951234567896"
   For i=1 to NumberOfUserIDs
       ! Read next user record
       Read #1,UserID$,UserPassword$,...
&              KillEnableFlag$,UserInitialProgram$
       Print #2,UserID$;" ";Userpassword$;" ";...
&               KillEnableFlag$;"   ";UserInitialProgram$
   Next i
   ReleaseDataBase
   close #2
   Goto AskCommand

PrintAccessLog: ! Wants to print out access log
   if len(line$)>15
   then create #2,right$(line$,16)
   else create #2,"console:"
   LockDataBase
   Position #1@NumberOfUserIDs*UserRecordSize+UserRecordBase
   Repeat
       Input #1,Line$ \ ! Fetch access log line
       If not(EOF(1)) Then Print #2,Line$ \ ! Print to output file
   Unless EOF(1) End
   ReleaseDataBase
   close #2
   Goto AskCommand

ResetAccessLog: ! Wants to reset access log to empty
   SetupToModifyDataBase
   Position #1@NumberOfUserIDs*UserRecordSize+UserRecordBase
   Syscall #1,CCSetFileSize$ \ ! Chop file just past User records
   ReleaseDataBase
   Goto AskCommand
!^L
ChangeSystemID: ! Wants to change SystemID
   Input "New System ID: " Line$
   SetupToModifyDataBase
   len(SystemID$)=maxlen(SystemID$)
   SystemID$[1,len(SystemID$)]=Line$ \ ! Blank fill
   Write #1@0,LogoffInterlock$,SystemID$,NumberOfUserIDs
   ReleaseDataBase
   Goto AskCommand

END
