<> <> <> <> <> <> <<>> DIRECTORY Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, Digit, ESC, Letter, SP], Basics USING [BITXOR, RawWords], Booting USING [CheckpointProc, RegisterProcs, RollbackProc], ConvertUnsafe USING [AppendRope, ToRope], DefaultRemoteNames USING [Get], DESFace USING [Block, DecryptBlock, EncryptBlock, MakeKey], Disk USING [DriveAttributes], File USING [wordsPerPage], GermSwap USING [LP, mdsiGerm], GVBasics USING [maxRNameLength], GVNames USING [Authenticate], IO USING [EraseChar, GetChar, PutChar, PutRope, STREAM], PhysicalVolume USING [NextPhysical, Physical, PhysicalInfo, ReadCredentials, WriteCredentials], PrincOps USING [GFT, SD, sGFTLength], Process USING [Detach, InitializeMonitor, Pause, SecondsToTicks], Rope USING [Concat, Equal, Fetch, Find, FromChar, Length, ROPE, Substr], UserCredentials USING [CredentialsChangeProc, defaultOptions, LoginOptions, State], UserCredentialsBackdoor USING [GuestProcsRec], VM USING [AddressForPageNumber, Allocate, Free, Interval, PageCount, PagesForWords, SwapIn, WordsForPages]; UserCredentialsImpl: MONITOR IMPORTS Ascii, Basics, Booting, ConvertUnsafe, DESFace, DefaultRemoteNames, Disk, GermSwap, GVNames, IO, PhysicalVolume, Process, Rope, VM EXPORTS PhysicalVolume, UserCredentials, UserCredentialsBackdoor = BEGIN OPEN UserCredentials; ROPE: TYPE = Rope.ROPE; <> IllegalParameter: ERROR = CODE; Bug: ERROR = CODE; DiskNotReady: ERROR = CODE; BrokenDisk: ERROR = CODE; <> credentialsImplID: CARDINAL = 08312; obsoleteImplID: CARDINAL = credentialsImplID - 1; -- anything other than credentialsImplID decryptedID: DESFace.Block = ALL[credentialsImplID]; Credentials: TYPE = LONG POINTER TO CredentialsObject; CredentialsObject: PUBLIC TYPE = MACHINE DEPENDENT RECORD [ implementationID(0): CARDINAL _ credentialsImplID, checksum(1): CARDINAL _ 0, fill(2:0..13): CARDINAL[0..17777B] _ 0, kind(2:14..14+2+6*16-1): SELECT state(2:14..15): State FROM noCredentials => NULL, name => [ encryptedID(3): DESFace.Block _ NULL, userName(7): StringBody _ [length: 0, maxlength: NULL, text: NULL] <> ], nameHint, nameAndPassword => [ userName(3): StringBody _ [length: 0, maxlength: NULL, text: NULL] <> <> ], ENDCASE ]; bogusCredentials: CredentialsObject = [implementationID: obsoleteImplID, kind: noCredentials[]]; <<>> <> <> GermString: TYPE = LONG POINTER TO LONG STRING; germGFT: LONG POINTER = GermSwap.LP[PrincOps.GFT, GermSwap.mdsiGerm]; germSD: LONG POINTER TO Basics.RawWords = GermSwap.LP[PrincOps.SD, GermSwap.mdsiGerm]; gftLen: CARDINAL = germSD[PrincOps.sGFTLength]; nameInGerm: GermString = LOOPHOLE[germGFT+gftLen]; passwordInGerm: GermString = nameInGerm + SIZE[LONG STRING]; germFreeSpace: LONG POINTER = passwordInGerm + SIZE[LONG STRING]; germFreeSpaceCount: CARDINAL = VM.WordsForPages[VM.PagesForWords[gftLen]] - (gftLen + 2*SIZE[LONG STRING]); CredentialsChange: TYPE = RECORD [proc: CredentialsChangeProc, clientData: REF ANY]; CredentialsChangeList: TYPE = LIST OF CredentialsChange; <> credentials: Credentials _ NIL; credentialsBuffer: VM.Interval; credentialsPV: PhysicalVolume.Physical; procs: CredentialsChangeList _ NIL; in, out: IO.STREAM _ NIL; checkpointing: BOOL _ FALSE; rolledBack: CONDITION _ [timeout: 0]; checkpointName, checkpointPassword: ROPE _ NIL; <<>> IsGuestProcess: PROC [] RETURNS [isGuest: BOOL] _ FalseProc; GuestProcs: REF UserCredentialsBackdoor.GuestProcsRec _ NEW[UserCredentialsBackdoor.GuestProcsRec _ [FalseProc]] ; <<>> <> FalseProc: SAFE PROC RETURNS [isGuest: BOOL] = CHECKED { isGuest _ FALSE; }; <> RegisterGuestProcs: PUBLIC SAFE PROC [newProcs: REF UserCredentialsBackdoor.GuestProcsRec] = CHECKED { GuestProcs _ newProcs; IsGuestProcess _ newProcs.IsGuestProcess; }; <> Login: PUBLIC ENTRY SAFE PROC [ startInteraction: SAFE PROC RETURNS [in, out: IO.STREAM], endInteraction: SAFE PROC [in, out: IO.STREAM], options: LoginOptions _ defaultOptions] = TRUSTED { ENABLE UNWIND => NULL; dirty: BOOL _ FALSE; terminalOn: BOOL _ FALSE; oldName, oldPassword: ROPE; newName, newPassword: ROPE; EnsureTerminalOn: PROC = { IF ~terminalOn THEN {[in, out] _ startInteraction[]; terminalOn _ TRUE}; }; EnsureTerminalOff: PROC = { IF terminalOn THEN {endInteraction[in, out]; in _ out _ NIL; terminalOn _ FALSE}; }; HandleDiskNotReady: PROC = { EnsureTerminalOn[]; IO.PutRope[out, "Disk is not ready...will retry..."]; Process.Pause[Process.SecondsToTicks[5]]; IO.PutRope[out, "retrying\N"]; }; CleanupOnAbort: PROC = { IF options.ignoreDiskEntirely THEN FreeCredentials[] ELSE ReleaseDiskCredentials[FALSE]; SetCredentialsInGerm[oldName, oldPassword]; NotifyCredentialsChangeProcs[]; EnsureTerminalOff[]; }; EnsureInitialized[]; IF IsGuestProcess[] THEN GuestProcs.Login[startInteraction, endInteraction, options]; IF ~options.alwaysInteract AND nameInGerm^.length > 0 THEN <> RETURN; [oldName, oldPassword] _ GetInternal[]; AllocateCredentials[]; IF options.ignoreDiskEntirely THEN credentials^ _ bogusCredentials ELSE AcquireDiskCredentials[ ! DiskNotReady => {HandleDiskNotReady[]; RETRY}]; IF credentials.implementationID = credentialsImplID THEN { CheckCredentials: PROC [credentials: Credentials] RETURNS [ok: BOOL _ TRUE] = { Downgrade: PROC [state: State] = { [] _ ChangeStateInternal[state]; dirty _ TRUE; ok _ FALSE}; WITH cred: credentials SELECT FROM nameHint => { diskUserName: ROPE = ConvertUnsafe.ToRope[@cred.userName]; EnsureTerminalOn[]; SetCredentialsInGerm[diskUserName, NIL]; [] _ GetAndAuthenticate[ ! UNWIND => CleanupOnAbort[]]; IF (dirty _ ~diskUserName.Equal[ConvertUnsafe.ToRope[nameInGerm^]]) THEN [] _ ChangeStateInternal[nameHint]; }; nameAndPassword => { diskUserName: ROPE = ConvertUnsafe.ToRope[@cred.userName]; diskPassword: ROPE = ConvertUnsafe.ToRope[@cred.userName + SIZE[StringBody[diskUserName.Length[]]]]; SetCredentialsInGerm[diskUserName, diskPassword]; SELECT GVNames.Authenticate[diskUserName, diskPassword] FROM individual, allDown => NULL; notFound, group => Downgrade[nameHint]; badPwd => Downgrade[name]; ENDCASE => ERROR Bug; }; name => { EnsureTerminalOn[]; SetCredentialsInGerm[ConvertUnsafe.ToRope[@cred.userName], NIL]; DO -- loops only if GV down and password supplied doesn't work. block: DESFace.Block; SELECT GetAndAuthenticate[passwordOnly ! UNWIND => CleanupOnAbort[]] FROM ok => { <> DESFace.DecryptBlock[ key: DESFace.MakeKey[passwordInGerm^], from: @cred.encryptedID, to: @block]; IF (dirty _ dirty OR (block ~= decryptedID)) THEN [] _ ChangeStateInternal[name]; }; bogusGVName => Downgrade[nameHint]; gvDown => { <> DESFace.DecryptBlock[ key: DESFace.MakeKey[passwordInGerm^], from: @cred.encryptedID, to: @block]; IF block ~= decryptedID THEN {IO.PutRope[out, "Wrong credentials!\N"]; LOOP}; }; ENDCASE; EXIT ENDLOOP; }; ENDCASE => ERROR Bug; }; UNTIL CheckCredentials[credentials] DO ENDLOOP; } ELSE { ENABLE UNWIND => CleanupOnAbort[]; RewriteDisk: PROCEDURE RETURNS [rewrite: BOOLEAN] = INLINE { IF options.ignoreDiskEntirely THEN RETURN[FALSE]; IF ~options.confirmCredentialsOverwrite THEN RETURN[TRUE]; RETURN[~Confirm["Disk credentials missing or obsolete; shall I leave it that way? "]] }; EnsureTerminalOn[]; IF (dirty _ RewriteDisk[]) THEN [] _ ChangeStateInternal[ IF GetAndAuthenticate[] = ok AND ~options.prohibitDiskProtection AND Confirm["Do you wish to prevent others from accessing your disk? "] THEN name ELSE nameHint ] ELSE [] _ GetAndAuthenticate[]; }; IF options.ignoreDiskEntirely THEN FreeCredentials[] ELSE ReleaseDiskCredentials[dirty ! BrokenDisk => { EnsureTerminalOn[]; IO.PutRope[out, "Disk error rewriting credentials file...I give up\N"]; DO ENDLOOP; }; DiskNotReady => {HandleDiskNotReady[]; RETRY}; ]; EnsureTerminalOff[]; [newName, newPassword] _ GetInternal[]; IF ~(oldName.Equal[newName, FALSE] AND oldPassword.Equal[newPassword]) THEN NotifyCredentialsChangeProcs[]; }; GetState: PUBLIC ENTRY SAFE PROC RETURNS [state: State] = TRUSTED { ENABLE UNWIND => NULL; IF IsGuestProcess[] THEN RETURN GuestProcs.GetState[]; AcquireDiskCredentials[]; state _ credentials.state; ReleaseDiskCredentials[FALSE]; }; ChangeState: PUBLIC ENTRY SAFE PROC [new: State] RETURNS [old: State] = TRUSTED { <> ENABLE UNWIND => NULL; IF IsGuestProcess[] THEN RETURN GuestProcs.ChangeState[new]; AcquireDiskCredentials[]; old _ ChangeStateInternal[new]; ReleaseDiskCredentials[TRUE]; }; Get: PUBLIC ENTRY SAFE PROC RETURNS [name, password: ROPE] = TRUSTED { ENABLE UNWIND => NULL; EnsureInitialized[]; IF IsGuestProcess[] THEN RETURN GuestProcs.Get[]; [name, password] _ GetInternal[]; }; RegisterForChange: PUBLIC ENTRY SAFE PROC [proc: CredentialsChangeProc, clientData: REF ANY _ NIL] = TRUSTED { procs _ CONS[[proc, clientData], procs]; }; UnRegisterForChange: PUBLIC ENTRY SAFE PROC [proc: CredentialsChangeProc, clientData: REF ANY _ NIL] = TRUSTED { prev: CredentialsChangeList _ NIL; FOR cur: CredentialsChangeList _ procs, cur.rest UNTIL cur = NIL DO IF cur.first.proc = proc AND cur.first.clientData = clientData THEN { IF prev = NIL THEN procs _ cur.rest ELSE prev.rest _ cur.rest; EXIT }; prev _ cur; ENDLOOP; }; <<>> <<*** For emergency use only ***>> BreakIn: PROC = { BreakInInternal: ENTRY PROC = INLINE { AllocateCredentials[]; [] _ ChangeStateInternal[noCredentials]; ReleaseDiskCredentials[TRUE]; }; Process.InitializeMonitor[@LOCK]; BreakInInternal[]; }; <> EnsureInitialized: INTERNAL PROC = { <> WHILE checkpointing DO WAIT rolledBack; ENDLOOP; IF nameInGerm^ = NIL THEN SetCredentialsInGerm[NIL, NIL]; }; GetInternal: PROC RETURNS [name, password: ROPE] = TRUSTED { name _ ConvertUnsafe.ToRope[nameInGerm^]; password _ ConvertUnsafe.ToRope[passwordInGerm^]; }; SetCredentialsInGerm: PROC [name, password: ROPE] = { nameLen: NAT = name.Length[]; passwordLen: NAT = password.Length[]; IF SIZE[StringBody[nameLen]] + SIZE[StringBody[passwordLen]] > germFreeSpaceCount OR nameLen > GVBasics.maxRNameLength OR passwordLen > GVBasics.maxRNameLength THEN ERROR IllegalParameter; nameInGerm^ _ germFreeSpace; nameInGerm^^ _ StringBody[maxlength: nameLen, text: ]; ConvertUnsafe.AppendRope[to: nameInGerm^, from: name]; passwordInGerm^ _ nameInGerm^ + SIZE[StringBody[nameLen]]; passwordInGerm^^ _ StringBody[maxlength: passwordLen, text: ]; ConvertUnsafe.AppendRope[to: passwordInGerm^, from: password]; }; ChangeStateInternal: PROC [new: State] RETURNS [old: State] = { MoveString: PROC [new, old: LONG STRING] = { ConvertUnsafe.AppendRope[to: new, from: ConvertUnsafe.ToRope[old]]}; words: CARDINAL; old _ credentials.state; SELECT new FROM noCredentials => { credentials^ _ bogusCredentials; words _ SIZE[noCredentials CredentialsObject]; }; name => { credentials^ _ [kind: name[encryptedID: , userName: StringBody[maxlength: nameInGerm^.length, text: ]]]; WITH cred: credentials SELECT FROM name => { block: DESFace.Block _ decryptedID; DESFace.EncryptBlock[ key: DESFace.MakeKey[passwordInGerm^], from: @block, to: @cred.encryptedID]; MoveString[@cred.userName, nameInGerm^]; }; ENDCASE => ERROR Bug; words _ SIZE[name CredentialsObject] + (SIZE[StringBody[nameInGerm^.length]] - SIZE[StringBody[0]]); }; nameHint => { credentials^ _ [kind: nameHint[userName: StringBody[maxlength: nameInGerm^.length, text: ]]]; WITH cred: credentials SELECT FROM nameHint => MoveString[@cred.userName, nameInGerm^]; ENDCASE => ERROR Bug; words _ SIZE[nameHint CredentialsObject] + (SIZE[StringBody[nameInGerm^.length]] - SIZE[StringBody[0]]); }; nameAndPassword => { credentials^ _ [kind: nameAndPassword[userName: StringBody[maxlength: nameInGerm^.length, text: ]]]; WITH cred: credentials SELECT FROM nameAndPassword => { diskUserName: LONG STRING = @cred.userName; diskPassword: LONG STRING = @cred.userName + SIZE[StringBody[nameInGerm^.length]]; MoveString[@cred.userName, nameInGerm^]; diskPassword^ _ StringBody[maxlength: passwordInGerm^.length, text: ]; MoveString[diskPassword, passwordInGerm^]; }; ENDCASE => ERROR Bug; words _ SIZE[nameAndPassword CredentialsObject] + (SIZE[StringBody[nameInGerm^.length]] - SIZE[StringBody[0]]) + SIZE[StringBody[passwordInGerm^.length]]; }; ENDCASE => ERROR Bug; credentials.checksum _ ComputeChecksum[DESCRIPTOR[credentials, words]]; }; ComputeChecksum: PROC [input: LONG DESCRIPTOR FOR ARRAY OF WORD] RETURNS [checksum: WORD _ 0] = { FOR i: CARDINAL IN [0..input.LENGTH) DO checksum _ Basics.BITXOR[checksum, input[i]]; ENDLOOP; }; ValidChecksum: PROC [input: LONG DESCRIPTOR FOR ARRAY OF WORD] RETURNS [ok: BOOLEAN] = { checksum: WORD _ 0; FOR i: CARDINAL IN [0..input.LENGTH) DO checksum _ Basics.BITXOR[checksum, input[i]]; ENDLOOP; RETURN[checksum = 0] }; GetAndAuthenticate: PROC [askFor: {both, passwordOnly} _ both] RETURNS [outcome: {ok, bogusGVName, gvDown} _ ok] = { Rubout: ERROR = CODE; GetID: PROC [default: ROPE, echo: BOOL _ TRUE] RETURNS [id: ROPE] = { OPEN Ascii; firstTime: BOOLEAN _ TRUE; c: CHAR; EraseAll: PROC = { IF echo THEN FOR i: INT DECREASING IN [0..id.Length[]) DO out.EraseChar[id.Fetch[i]]; ENDLOOP; id _ NIL; }; Done: PROC [c: CHAR] RETURNS [BOOL] = INLINE { IF firstTime THEN { SELECT c FROM ControlA, BS, ControlQ, ControlW, ControlX, CR, SP => NULL; ENDCASE => EraseAll[]; firstTime _ FALSE; }; RETURN[c = SP OR c = CR] }; id _ default; IF echo THEN IO.PutRope[out, default]; c _ in.GetChar[]; UNTIL Done[c] DO SELECT c FROM DEL => ERROR Rubout; ControlA, BS => { len: INT _ id.Length[]; IF len > 0 THEN { len _ len - 1; IF echo THEN out.EraseChar[id.Fetch[len]]; id _ id.Substr[len: len]; }; }; ControlW, ControlQ => { <, the and following are to be removed.>> alpha: BOOL _ FALSE; FOR i: INT DECREASING IN [0..id.Length[]) DO ch: CHAR = id.Fetch[i]; IF Ascii.Letter[ch] OR Ascii.Digit[ch] THEN alpha _ TRUE ELSE IF alpha THEN {id _ id.Substr[len: i + 1]; EXIT}; IF echo THEN out.EraseChar[ch]; REPEAT FINISHED => id _ NIL; ENDLOOP; }; ControlX => EraseAll[]; ENDCASE => {id _ id.Concat[Rope.FromChar[c]]; IF echo THEN out.PutChar[c]}; c _ in.GetChar[]; ENDLOOP; }; name, password: ROPE _ NIL; cancelString: ROPE = " XXX\N"; DO ENABLE UNWIND => IO.PutRope[out, cancelString]; name _ ConvertUnsafe.ToRope[nameInGerm^]; IO.PutRope[out, "Name: "]; IF askFor = passwordOnly THEN IO.PutRope[out, name] ELSE { name _ GetID[name ! Rubout => {IO.PutRope[out, cancelString]; LOOP}]; IF name.Find["."] = -1 THEN { defaultRegistry: ROPE = Rope.Concat[".", DefaultRemoteNames.Get[].registry]; IO.PutRope[out, defaultRegistry]; name _ name.Concat[defaultRegistry]}; }; IO.PutRope[out, " password: "]; password _ GetID[NIL, FALSE ! Rubout => {IO.PutRope[out, cancelString]; LOOP}]; SetCredentialsInGerm[name, password]; IO.PutRope[out, " GV..."]; SELECT GVNames.Authenticate[name, password] FROM individual => {IO.PutRope[out, "ok\N"]; EXIT}; notFound, group => { IO.PutRope[out, "invalid user name"]; IF askFor = passwordOnly THEN {out.PutChar['\N]; outcome _ bogusGVName; EXIT}; }; badPwd => IO.PutRope[out, "incorrect password"]; allDown => { IO.PutRope[out, "Grapevine down, proceeding anyway\N"]; outcome _ gvDown; EXIT}; ENDCASE; IO.PutRope[out, ", try again.\N"]; ENDLOOP; }; Confirm: PROCEDURE [message: ROPE _ NIL] RETURNS [BOOL] = { DO IF message ~= NIL THEN IO.PutRope[out, message]; SELECT in.GetChar[ ! UNWIND => IO.PutRope[out, " XXX\N"]] FROM 'y, 'Y, Ascii.SP, Ascii.CR, Ascii.ESC => {IO.PutRope[out, "Yes\N"]; RETURN[TRUE]}; 'n, 'N, Ascii.DEL => {IO.PutRope[out, "No\N"]; RETURN[FALSE]}; ENDCASE; ENDLOOP}; <> <<>> NotifyCredentialsChangeProcs: PROC = { <> Process.Detach[FORK NotifyProcess[CopyCredentialsChangeList[procs]]]; }; NotifyProcess: PROC [list: CredentialsChangeList] = { WHILE list # NIL DO list.first.proc[list.first.clientData]; list _ list.rest; ENDLOOP; }; CopyCredentialsChangeList: PROC [list: CredentialsChangeList] RETURNS [head: CredentialsChangeList _ NIL] = { IF list # NIL THEN { tail: CredentialsChangeList _ head _ LIST[list.first]; DO list _ list.rest; IF list = NIL THEN EXIT; tail _ tail.rest _ LIST[list.first]; ENDLOOP; }; }; <> <<>> AllocateCredentials: PROC = { diskCredentialsPages: VM.PageCount = VM.PagesForWords[File.wordsPerPage]; <> <= SIZE[nameAndPassword CredentialsObject] - SIZE[StringBody[0]] +>> <<2*SIZE[StringBody[GVBasics.maxRNameLength]]];>> credentialsBuffer _ VM.Allocate[diskCredentialsPages]; credentials _ VM.AddressForPageNumber[credentialsBuffer.page]; VM.SwapIn[interval: credentialsBuffer, kill: TRUE, pin: TRUE]; }; FreeCredentials: PROC = { VM.Free[credentialsBuffer]; credentials _ NIL; }; AcquireDiskCredentials: PROC = { IF credentialsPV = NIL THEN GetCredentialsVolume[]; IF credentials = NIL THEN AllocateCredentials[]; SELECT PhysicalVolume.ReadCredentials[credentialsPV, credentials] FROM ok => { words: CARDINAL; WITH cred: credentials SELECT FROM noCredentials => words _ SIZE[noCredentials CredentialsObject]; name => words _ SIZE[name CredentialsObject] + (SIZE[StringBody[cred.userName.length]] - SIZE[StringBody[0]]); nameHint => words _ SIZE[nameHint CredentialsObject] + (SIZE[StringBody[cred.userName.length]] - SIZE[StringBody[0]]); nameAndPassword => { diskUserName: LONG STRING = @cred.userName; diskPassword: LONG STRING = @cred.userName + SIZE[StringBody[diskUserName.length]]; words _ SIZE[nameAndPassword CredentialsObject] + (SIZE[StringBody[diskUserName.length]] - SIZE[StringBody[0]]) + SIZE[StringBody[diskPassword.length]]; }; ENDCASE; IF ~ValidChecksum[DESCRIPTOR[credentials, words]] THEN credentials^ _ bogusCredentials; }; hardware, software => <> credentials^ _ bogusCredentials; wentOffline => ERROR DiskNotReady; ENDCASE => ERROR Bug; }; ReleaseDiskCredentials: PROC [dirty: BOOL] = { IF dirty THEN { IF credentialsPV = NIL THEN GetCredentialsVolume[]; SELECT PhysicalVolume.WriteCredentials[credentialsPV, credentials] FROM ok => NULL; hardware => ERROR BrokenDisk; wentOffline => ERROR DiskNotReady; ENDCASE => ERROR Bug; }; FreeCredentials[]; }; GetCredentialsVolume: PROC = { UNTIL (credentialsPV _ PhysicalVolume.NextPhysical[credentialsPV]) = NIL OR Disk.DriveAttributes[PhysicalVolume.PhysicalInfo[credentialsPV].channel].ordinal = 0 DO ENDLOOP; }; Checkpointing: ENTRY Booting.CheckpointProc = TRUSTED { EnsureInitialized[]; checkpointing _ TRUE; [checkpointName, checkpointPassword] _ GetInternal[]; }; RollingBack: ENTRY Booting.RollbackProc = TRUSTED { name, password: ROPE; IF ~checkpointing THEN ERROR; checkpointing _ FALSE; EnsureInitialized[]; [name, password] _ GetInternal[]; IF ~(name.Equal[checkpointName, FALSE] AND password.Equal[checkpointPassword]) THEN NotifyCredentialsChangeProcs[]; BROADCAST rolledBack; }; <> Initialize: ENTRY PROC = { EnsureInitialized[]; Booting.RegisterProcs[c: Checkpointing, r: RollingBack]; }; Initialize[]; END. <> <> <<>> <> <> <<>>