UserCredentialsImpl.mesa
last edited by Levin on June 27, 1983 5:49 pm
DIRECTORY
Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, ESC, SP],
ConvertUnsafe USING [AppendRope, ToRope],
DebuggerSwap USING [EnableSwatWatcher],
DESFace USING [Block, DecryptBlock, EncryptBlock, MakeKey],
Disk USING [DriveAttributes],
File USING [wordsPerPage],
GermSwap USING [mdsiGerm, switches],
GVBasics USING [maxRNameLength],
GVNames USING [Authenticate],
IO USING [EraseChar, GetChar, PutChar, PutRope, STREAM],
PhysicalVolume
USING [
NextPhysical, Physical, PhysicalInfo, ReadCredentials, WriteCredentials],
PrincOps USING [GFT, LongNumber, SD, sGFTLength],
PrincOpsUtils USING [BITXOR],
Process USING [InitializeMonitor, Pause, SecondsToTicks],
Rope USING [Concat, Digit, Equal, Fetch, Find, FromChar, Length, Letter, ROPE, Substr],
SimpleTerminal USING [TurnOff, TurnOn],
UserCredentials USING [defaultOptions, LoginOptions, State],
VM
USING [
Allocate, Free, Interval, PageCount, PageNumberToAddress, SwapIn, wordsPerPage, WordsToPages];
UserCredentialsImpl:
MONITOR
IMPORTS
ConvertUnsafe, DebuggerSwap, DESFace, Disk, GermSwap, GVNames, IO, PhysicalVolume, PrincOpsUtils, Process, Rope, SimpleTerminal, VM
EXPORTS PhysicalVolume, UserCredentials =
BEGIN
OPEN UserCredentials;
IllegalParameter: ERROR = CODE;
Bug: ERROR = CODE;
Declarations for data structures on disk
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]
The text portion of the userName follows here
],
nameHint, nameAndPassword => [
userName(3): StringBody ← [length: 0, maxlength: NULL, text: NULL]
The text portion of the userName follows here
If state = nameAndPassword, another StringBody follows
],
ENDCASE
];
bogusCredentials: CredentialsObject = [implementationID: obsoleteImplID, kind: noCredentials[]];
DiskNotReady: ERROR = CODE;
BrokenDisk: ERROR = CODE;
Declarations for data structures in memory (germ)
GermString: TYPE = LONG POINTER TO LONG STRING;
germMDSln: PrincOps.LongNumber = [num[highbits: GermSwap.mdsiGerm, lowbits: 0]];
germMDS: LONG POINTER = germMDSln.lp; -- compiler coughs if combined with above.
germGFT: LONG POINTER = LOOPHOLE[germMDS+LOOPHOLE[PrincOps.GFT, CARDINAL]];
germSD:
LONG
POINTER
TO
ARRAY [0..0)
OF
UNSPECIFIED =
LOOPHOLE[germMDS+LOOPHOLE[PrincOps.SD, CARDINAL]];
nameInGerm: GermString =
LOOPHOLE[germGFT+LOOPHOLE[germSD[PrincOps.sGFTLength], CARDINAL]];
passwordInGerm: GermString = nameInGerm + SIZE[LONG STRING];
germFreeSpace: LONG POINTER = passwordInGerm + SIZE[LONG STRING];
germFreeSpaceCount:
CARDINAL =
VM.WordsToPages[germSD[PrincOps.sGFTLength]] blows up the compiler
(germSD[PrincOps.sGFTLength] + VM.wordsPerPage - 1)/VM.wordsPerPage -
(germSD[PrincOps.sGFTLength] + 2*SIZE[LONG STRING]);
ROPE: TYPE = Rope.ROPE;
Global Variables (protected by the monitor)
credentials: Credentials ← NIL;
credentialsBuffer: VM.Interval;
credentialsPV: PhysicalVolume.Physical;
in, out: IO.STREAM ← NIL;
Exports to UserCredentials
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;
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[NIL, NIL];
EnsureTerminalOff[];
};
EnsureInitialized[];
IF ~options.alwaysInteract AND nameInGerm^.length > 0 THEN RETURN; -- already logged in
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 ← ~Rope.Equal[diskUserName, ToRope[nameInGerm^],
FALSE])
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 => {
Grapevine is up, and the credentials are valid. The password used to encrypt the information in the credentials file may not be the same one that was used (successfully) to authenticate the user to Grapevine. After all, the user may have changed his password in the Grapevine database since this procedure was last called. However, since the user name presented to Grapevine was forced to come from the disk and Grapevine authenticated the user, we can be confident that this is the same user who requested that the encrypted information be stored on the disk originally. We therefore encrypt with the new password and rewrite the credentials file. In effect, this gives us a cache of the last successful Grapevine authentication, which will be useful if Grapevine proves to be down on a subsequent Login.
DESFace.DecryptBlock[
key: DESFace.MakeKey[passwordInGerm^],
from: @cred.encryptedID, to: @block];
IF (dirty ← dirty
OR (block ~= decryptedID))
THEN
[] ← ChangeStateInternal[name];
};
bogusGVName => Downgrade[nameHint];
gvDown => {
Grapevine is down, so we must fall back on our cached information on the disk. We use the password supplied by the user to decrypt then compare it with the expected value. If it matches, the user is assumed to be authentic, and the password he supplied is highly likely to be the one he last successfully used to authenticate himself with Grapevine.
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[];
};
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 {
The internal signals from AcquireDiskCredentials and ReleaseDiskCredentials are presently uncaught, since we don't have any better way of dealing with them.
AcquireDiskCredentials[];
old ← ChangeStateInternal[new];
ReleaseDiskCredentials[TRUE];
};
Get:
PUBLIC
ENTRY
SAFE PROC RETURNS [name, password:
ROPE] =
TRUSTED {
EnsureInitialized[];
name ← ConvertUnsafe.ToRope[nameInGerm^];
password ← ConvertUnsafe.ToRope[passwordInGerm^];
};
*** For emergency use only ***
BreakIn:
PROC = {
BreakInInternal:
ENTRY
PROC =
INLINE {
AllocateCredentials[];
[] ← ChangeStateInternal[noCredentials];
ReleaseDiskCredentials[TRUE];
};
Process.InitializeMonitor[@LOCK];
BreakInInternal[];
};
Internal procedures
EnsureInitialized:
PROC = {
Note: one might think that this could be done at module start time, rather than on every call to a public procedure of this module. However, the contents of the germ mds can change after this module is started (e.g., a checkpoint is taken, then a physical boot causes a rollback).
IF nameInGerm^ = NIL THEN SetCredentialsInGerm[NIL, NIL]};
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 ← PrincOpsUtils.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 ← PrincOpsUtils.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 ← in.GetChar[];
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];
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 => {
text to be backed up is of the form ...<non-alpha><alpha><non-alpha>, the <alpha> and following <non-alpha> are to be removed.
alpha: BOOL ← FALSE;
FOR i:
INT
DECREASING
IN [0..id.Length[])
DO
ch: CHAR = id.Fetch[i];
IF Rope.Letter[ch] OR Rope.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};
Disk access
AllocateCredentials:
PROC = {
diskCredentialsPages:
VM.PageCount =
VM.WordsToPages[File.wordsPerPage];
PhysicalVolume assumes that the credentials occupy a maximum of File.wordsPerPage words. We assert that:
File.wordsPerPage >= SIZE[nameAndPassword CredentialsObject] - SIZE[StringBody[0]] +
2*SIZE[StringBody[GVBasics.maxRNameLength]]];
credentialsBuffer ← VM.Allocate[diskCredentialsPages];
credentials ← VM.PageNumberToAddress[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 =>
The disk is there, but it doesn't seem to work, treat as "no credentials" now.
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;
};
Start code
Initialize: ENTRY PROC = {EnsureInitialized[]};
Initialize[];
IF ~GermSwap.switches[n]
THEN {
turnOnProc:
SAFE
PROC
RETURNS [in, out:
IO.
STREAM] =
CHECKED {
[in, out] ← SimpleTerminal.TurnOn[];
out.PutRope["\NLogin please...\N"];
};
turnOffProc: SAFE PROC [in, out: IO.STREAM] = CHECKED {SimpleTerminal.TurnOff[]};
Login[startInteraction: turnOnProc, endInteraction: turnOffProc];
DebuggerSwap.EnableSwatWatcher[TRUE];
};
END.