<> <> <> DIRECTORY Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, Digit, ESC, Letter, SP], Basics USING [BITXOR], Booting USING [CheckpointProc, RegisterProcs, RollbackProc], ConvertUnsafe USING [AppendRope, ToRope], 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 [InitializeMonitor, Pause, SecondsToTicks], Rope USING [Concat, Equal, Fetch, Find, FromChar, Length, ROPE, Substr], UserCredentials USING [CredentialsChangeProc, defaultOptions, LoginOptions, State], VM USING [ AddressForPageNumber, Allocate, Free, Interval, PageCount, PagesForWords, SwapIn, wordsPerPage]; UserCredentialsImpl: MONITOR IMPORTS Ascii, Basics, Booting, ConvertUnsafe, DESFace, Disk, GermSwap, GVNames, IO, PhysicalVolume, Process, Rope, VM EXPORTS PhysicalVolume, UserCredentials = BEGIN OPEN UserCredentials; IllegalParameter: ERROR = CODE; Bug: 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[]]; DiskNotReady: ERROR = CODE; BrokenDisk: ERROR = CODE; <<>> <> GermString: TYPE = LONG POINTER TO LONG STRING; germGFT: LONG POINTER = GermSwap.LP[PrincOps.GFT, GermSwap.mdsiGerm]; germSD: LONG POINTER TO ARRAY [0..0) OF UNSPECIFIED = GermSwap.LP[PrincOps.SD, GermSwap.mdsiGerm]; nameInGerm: GermString = LOOPHOLE[germGFT+LOOPHOLE[germSD[PrincOps.sGFTLength], CARDINAL]]; passwordInGerm: GermString = nameInGerm + SIZE[LONG STRING]; germFreeSpace: LONG POINTER = passwordInGerm + SIZE[LONG STRING]; germFreeSpaceCount: CARDINAL = <> (germSD[PrincOps.sGFTLength] + VM.wordsPerPage - 1)/VM.wordsPerPage - (germSD[PrincOps.sGFTLength] + 2*SIZE[LONG STRING]); ROPE: TYPE = Rope.ROPE; CredentialsChange: TYPE = RECORD [proc: CredentialsChangeProc, clientData: REF ANY]; <> credentials: Credentials _ NIL; credentialsBuffer: VM.Interval; credentialsPV: PhysicalVolume.Physical; procs: LIST OF REF CredentialsChange _ NIL; in, out: IO.STREAM _ NIL; checkpointing: BOOL _ FALSE; rolledBack: CONDITION _ [timeout: 0]; checkpointName, checkpointPassword: ROPE _ NIL; <<>> <> 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[]; out.PutRope["Disk is not ready...will retry..."]; Process.Pause[Process.SecondsToTicks[5]]; out.PutRope["retrying\N"]; }; CleanupOnAbort: PROC = { IF options.ignoreDiskEntirely THEN FreeCredentials[] ELSE ReleaseDiskCredentials[FALSE]; SetCredentialsInGerm[oldName, oldPassword]; NotifyCredentialsChangeProcs[]; EnsureTerminalOff[]; }; EnsureInitialized[]; IF ~options.alwaysInteract AND nameInGerm^.length > 0 THEN RETURN; -- already logged in [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 => { OPEN ConvertUnsafe; diskUserName: ROPE = ToRope[@cred.userName]; EnsureTerminalOn[]; SetCredentialsInGerm[diskUserName, NIL]; [] _ GetAndAuthenticate[ ! UNWIND => CleanupOnAbort[]]; IF (dirty _ ~diskUserName.Equal[ToRope[nameInGerm^]]) THEN [] _ ChangeStateInternal[nameHint]; }; nameAndPassword => { OPEN ConvertUnsafe; diskUserName: ROPE = ToRope[@cred.userName]; diskPassword: ROPE = 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 {out.PutRope["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[]; out.PutRope["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 { AcquireDiskCredentials[]; state _ credentials.state; ReleaseDiskCredentials[FALSE]; }; ChangeState: PUBLIC ENTRY SAFE PROC [new: State] RETURNS [old: State] = TRUSTED { <> AcquireDiskCredentials[]; old _ ChangeStateInternal[new]; ReleaseDiskCredentials[TRUE]; }; Get: PUBLIC ENTRY SAFE PROC RETURNS [name, password: ROPE] = TRUSTED { EnsureInitialized[]; [name, password] _ GetInternal[]; }; RegisterForChange: PUBLIC ENTRY SAFE PROC [ proc: CredentialsChangeProc, clientData: REF ANY _ NIL] = TRUSTED { procs _ CONS[NEW[CredentialsChange _ [proc, clientData]], procs]; }; UnRegisterForChange: PUBLIC ENTRY SAFE PROC [ proc: CredentialsChangeProc, clientData: REF ANY _ NIL] = TRUSTED { prev: LIST OF REF CredentialsChange _ NIL; FOR cur: LIST OF REF CredentialsChange _ 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 out.PutRope[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; defaultRegistry: ROPE = ".pa"; cancelString: ROPE = " XXX\N"; DO ENABLE UNWIND => out.PutRope[cancelString]; name _ ConvertUnsafe.ToRope[nameInGerm^]; out.PutRope["Name: "]; IF askFor = passwordOnly THEN out.PutRope[name] ELSE { name _ GetID[name ! Rubout => {out.PutRope[cancelString]; LOOP}]; IF name.Find["."] = -1 THEN { out.PutRope[defaultRegistry]; name _ name.Concat[defaultRegistry]}; }; out.PutRope[" password: "]; password _ GetID[NIL, FALSE ! Rubout => {out.PutRope[cancelString]; LOOP}]; SetCredentialsInGerm[name, password]; out.PutRope[" GV..."]; SELECT GVNames.Authenticate[name, password] FROM individual => {out.PutRope["ok\N"]; EXIT}; notFound, group => { out.PutRope["invalid user name"]; IF askFor = passwordOnly THEN {out.PutChar['\N]; outcome _ bogusGVName; EXIT}; }; badPwd => out.PutRope["incorrect password"]; allDown => { out.PutRope["Grapevine down, proceeding anyway\N"]; outcome _ gvDown; EXIT}; ENDCASE; out.PutRope[", try again.\N"]; ENDLOOP; }; Confirm: PROCEDURE [message: ROPE _ NIL] RETURNS [BOOL] = { DO IF message ~= NIL THEN out.PutRope[message]; SELECT in.GetChar[ ! UNWIND => out.PutRope[" XXX\N"]] FROM 'y, 'Y, Ascii.SP, Ascii.CR, Ascii.ESC => {out.PutRope["Yes\N"]; RETURN[TRUE]}; 'n, 'N, Ascii.DEL => {out.PutRope["No\N"]; RETURN[FALSE]}; ENDCASE; ENDLOOP}; <> <<>> NotifyCredentialsChangeProcs: PROC = { FOR list: LIST OF REF CredentialsChange _ procs, list.rest UNTIL list = NIL DO list.first.proc[list.first.clientData]; 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.